From 9c4ba757c289a2b26b27543856097f75b64d1925 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 08:54:20 -0700 Subject: [PATCH 01/34] add webgl renderer --- .../render/frame/derive/alliance-clusters.ts | 44 + .../render/frame/derive/attack-rings.ts | 43 + .../render/frame/derive/nuke-telegraphs.ts | 57 + .../render/frame/derive/player-status.ts | 74 + .../render/frame/derive/relation-matrix.ts | 92 + src/client/render/frame/index.ts | 20 + src/client/render/frame/railroad-cache.ts | 273 + src/client/render/frame/trail-manager.ts | 133 + src/client/render/frame/upload.ts | 135 + src/client/render/game-constants.ts | 163 + .../render/gl/assets/MissileSiloIconWhite.svg | 72 + .../render/gl/assets/emoji-atlas-meta.json | 68 + src/client/render/gl/assets/emoji-atlas.png | Bin 0 -> 219817 bytes .../render/gl/assets/flag-atlas-meta.json | 568 + src/client/render/gl/assets/flag-atlas.png | Bin 0 -> 1019134 bytes .../render/gl/assets/fx-atlas-meta.json | 78 + src/client/render/gl/assets/fx-atlas.png | Bin 0 -> 7860 bytes src/client/render/gl/assets/icon-atlas.png | Bin 0 -> 3295 bytes src/client/render/gl/assets/msdf-atlas.json | 29513 ++++++++++++++++ src/client/render/gl/assets/msdf-atlas.png | Bin 0 -> 522694 bytes .../render/gl/assets/status-atlas-meta.json | 19 + src/client/render/gl/assets/status-atlas.png | Bin 0 -> 93709 bytes src/client/render/gl/assets/unit-atlas.png | Bin 0 -> 322 bytes src/client/render/gl/camera.ts | 198 + src/client/render/gl/debug/config-prop.ts | 12 + src/client/render/gl/debug/folder.ts | 18 + src/client/render/gl/debug/index.ts | 28 + src/client/render/gl/debug/layout.ts | 747 + src/client/render/gl/debug/props/color.ts | 67 + src/client/render/gl/debug/props/select.ts | 29 + src/client/render/gl/debug/props/slider.ts | 31 + src/client/render/gl/debug/props/toggle.ts | 28 + src/client/render/gl/debug/tree.ts | 23 + src/client/render/gl/debug/wiring.ts | 215 + src/client/render/gl/dynamic-buffer.ts | 55 + src/client/render/gl/events.ts | 96 + src/client/render/gl/game-view.ts | 410 + src/client/render/gl/index.ts | 30 + src/client/render/gl/keyboard-pan.ts | 141 + src/client/render/gl/map-interaction.ts | 418 + src/client/render/gl/passes/bar-pass.ts | 262 + .../render/gl/passes/border-compute-pass.ts | 265 + .../render/gl/passes/border-stamp-pass.ts | 162 + .../render/gl/passes/conquest-popup-pass.ts | 412 + .../render/gl/passes/coordinate-grid-pass.ts | 135 + src/client/render/gl/passes/crosshair-pass.ts | 96 + .../render/gl/passes/fallout-bloom-pass.ts | 320 + .../render/gl/passes/fallout-light-pass.ts | 235 + .../gl/passes/fx-pass/fx-attack-ring-pass.ts | 212 + .../gl/passes/fx-pass/fx-shockwave-pass.ts | 191 + .../gl/passes/fx-pass/fx-sprite-pass.ts | 526 + src/client/render/gl/passes/fx-pass/index.ts | 126 + src/client/render/gl/passes/lightmap-pass.ts | 206 + .../render/gl/passes/move-indicator-pass.ts | 115 + .../render/gl/passes/name-pass/atlas-data.ts | 86 + .../gl/passes/name-pass/data-textures.ts | 89 + .../gl/passes/name-pass/debug-program.ts | 91 + .../gl/passes/name-pass/icon-program.ts | 186 + .../render/gl/passes/name-pass/index.ts | 573 + .../passes/name-pass/status-icon-program.ts | 163 + .../render/gl/passes/name-pass/text-layout.ts | 74 + .../gl/passes/name-pass/text-program.ts | 197 + .../render/gl/passes/name-pass/types.ts | 88 + .../render/gl/passes/night-composite-pass.ts | 114 + .../render/gl/passes/nuke-telegraph-pass.ts | 152 + .../render/gl/passes/nuke-trajectory-pass.ts | 328 + .../render/gl/passes/point-light-pass.ts | 237 + .../render/gl/passes/radial-menu-pass.ts | 574 + src/client/render/gl/passes/railroad-pass.ts | 340 + .../render/gl/passes/range-circle-pass.ts | 79 + .../render/gl/passes/sam-radius-pass.ts | 396 + .../render/gl/passes/selection-box-pass.ts | 103 + .../render/gl/passes/spawn-overlay-pass.ts | 176 + .../render/gl/passes/structure-level-pass.ts | 324 + src/client/render/gl/passes/structure-pass.ts | 435 + src/client/render/gl/passes/terrain-pass.ts | 77 + src/client/render/gl/passes/territory-pass.ts | 353 + src/client/render/gl/passes/trail-pass.ts | 106 + src/client/render/gl/passes/unit-pass.ts | 513 + src/client/render/gl/render-settings.json | 316 + src/client/render/gl/render-settings.ts | 253 + src/client/render/gl/renderer.ts | 1136 + src/client/render/gl/settings-utils.ts | 49 + .../render/gl/shaders/bar/bar.frag.glsl | 41 + .../render/gl/shaders/bar/bar.vert.glsl | 27 + .../border-compute/border-compute.frag.glsl | 123 + .../conquest-popup/conquest-popup.frag.glsl | 38 + .../conquest-popup/conquest-popup.vert.glsl | 89 + .../gl/shaders/crosshair/crosshair.frag.glsl | 32 + .../gl/shaders/crosshair/crosshair.vert.glsl | 22 + .../shaders/day-night/border-stamp.frag.glsl | 79 + .../shaders/day-night/border-stamp.vert.glsl | 10 + .../gl/shaders/day-night/composite.frag.glsl | 18 + .../day-night/fallout-composite.frag.glsl | 8 + .../day-night/fallout-composite.vert.glsl | 11 + .../shaders/day-night/fallout-light.frag.glsl | 40 + .../gl/shaders/day-night/light.frag.glsl | 20 + .../gl/shaders/day-night/light.vert.glsl | 29 + .../shaders/fallout-bloom/composite.frag.glsl | 10 + .../shaders/fallout-bloom/composite.vert.glsl | 11 + .../shaders/fallout-bloom/extract.frag.glsl | 82 + .../fallout-bloom/heat-decay.frag.glsl | 30 + .../gl/shaders/fx/attack-ring.frag.glsl | 40 + .../gl/shaders/fx/attack-ring.vert.glsl | 27 + .../render/gl/shaders/fx/shockwave.frag.glsl | 17 + .../render/gl/shaders/fx/shockwave.vert.glsl | 27 + .../render/gl/shaders/fx/sprite.frag.glsl | 15 + .../render/gl/shaders/fx/sprite.vert.glsl | 33 + src/client/render/gl/shaders/glsl.d.ts | 4 + .../render/gl/shaders/grid/grid.frag.glsl | 104 + .../gl/shaders/map-overlay/overlay.vert.glsl | 14 + .../shaders/map-overlay/territory.frag.glsl | 42 + .../gl/shaders/map-overlay/trail.frag.glsl | 31 + .../move-indicator/move-indicator.frag.glsl | 62 + .../move-indicator/move-indicator.vert.glsl | 20 + .../gl/shaders/name/debug-box.frag.glsl | 32 + .../gl/shaders/name/debug-box.vert.glsl | 108 + .../render/gl/shaders/name/icon.frag.glsl | 24 + .../render/gl/shaders/name/icon.vert.glsl | 154 + .../render/gl/shaders/name/name.frag.glsl | 62 + .../render/gl/shaders/name/name.vert.glsl | 156 + .../gl/shaders/name/status-icon.frag.glsl | 40 + .../gl/shaders/name/status-icon.vert.glsl | 217 + .../nuke-telegraph/nuke-telegraph.frag.glsl | 56 + .../nuke-telegraph/nuke-telegraph.vert.glsl | 27 + .../nuke-trajectory-marker.frag.glsl | 45 + .../nuke-trajectory-marker.vert.glsl | 33 + .../nuke-trajectory/nuke-trajectory.frag.glsl | 54 + .../nuke-trajectory/nuke-trajectory.vert.glsl | 48 + .../gl/shaders/radial-menu/arcs.frag.glsl | 132 + .../gl/shaders/radial-menu/arcs.vert.glsl | 24 + .../gl/shaders/radial-menu/icon.frag.glsl | 27 + .../gl/shaders/radial-menu/icon.vert.glsl | 74 + .../gl/shaders/railroad/railroad.frag.glsl | 155 + .../range-circle/range-circle.frag.glsl | 27 + .../range-circle/range-circle.vert.glsl | 24 + .../shaders/sam-radius/sam-radius.frag.glsl | 74 + .../shaders/sam-radius/sam-radius.vert.glsl | 33 + .../selection-box/selection-box.frag.glsl | 38 + .../selection-box/selection-box.vert.glsl | 22 + .../render/gl/shaders/shared/blur.frag.glsl | 16 + .../shaders/shared/fullscreen-no-uv.vert.glsl | 5 + .../gl/shaders/shared/fullscreen.vert.glsl | 8 + .../spawn-overlay/spawn-overlay.frag.glsl | 103 + .../structure-level/structure-level.frag.glsl | 50 + .../structure-level/structure-level.vert.glsl | 96 + .../gl/shaders/structure/structure.frag.glsl | 168 + .../gl/shaders/structure/structure.vert.glsl | 65 + .../gl/shaders/terrain/terrain.frag.glsl | 11 + .../gl/shaders/terrain/terrain.vert.glsl | 17 + .../render/gl/shaders/unit/unit.frag.glsl | 93 + .../render/gl/shaders/unit/unit.vert.glsl | 46 + src/client/render/gl/utils/affiliation.ts | 171 + src/client/render/gl/utils/color-utils.ts | 90 + src/client/render/gl/utils/gl-utils.ts | 209 + src/client/render/gl/utils/gpu-resources.ts | 78 + src/client/render/gl/utils/heat-manager.ts | 319 + src/client/render/gl/utils/nuke-trajectory.ts | 261 + src/client/render/gl/utils/tile-codec.ts | 18 + src/client/render/gl/vite-env.d.ts | 8 + src/client/render/types/frame-data.ts | 76 + src/client/render/types/frame-events.ts | 114 + src/client/render/types/frame-source.ts | 38 + src/client/render/types/game-updates.ts | 171 + src/client/render/types/game.ts | 17 + src/client/render/types/index.ts | 108 + src/client/render/types/renderer.ts | 208 + src/client/render/types/replay.ts | 144 + src/client/render/types/unit-type.ts | 83 + 169 files changed, 50402 insertions(+) create mode 100644 src/client/render/frame/derive/alliance-clusters.ts create mode 100644 src/client/render/frame/derive/attack-rings.ts create mode 100644 src/client/render/frame/derive/nuke-telegraphs.ts create mode 100644 src/client/render/frame/derive/player-status.ts create mode 100644 src/client/render/frame/derive/relation-matrix.ts create mode 100644 src/client/render/frame/index.ts create mode 100644 src/client/render/frame/railroad-cache.ts create mode 100644 src/client/render/frame/trail-manager.ts create mode 100644 src/client/render/frame/upload.ts create mode 100644 src/client/render/game-constants.ts create mode 100644 src/client/render/gl/assets/MissileSiloIconWhite.svg create mode 100644 src/client/render/gl/assets/emoji-atlas-meta.json create mode 100644 src/client/render/gl/assets/emoji-atlas.png create mode 100644 src/client/render/gl/assets/flag-atlas-meta.json create mode 100644 src/client/render/gl/assets/flag-atlas.png create mode 100644 src/client/render/gl/assets/fx-atlas-meta.json create mode 100644 src/client/render/gl/assets/fx-atlas.png create mode 100644 src/client/render/gl/assets/icon-atlas.png create mode 100644 src/client/render/gl/assets/msdf-atlas.json create mode 100644 src/client/render/gl/assets/msdf-atlas.png create mode 100644 src/client/render/gl/assets/status-atlas-meta.json create mode 100644 src/client/render/gl/assets/status-atlas.png create mode 100644 src/client/render/gl/assets/unit-atlas.png create mode 100644 src/client/render/gl/camera.ts create mode 100644 src/client/render/gl/debug/config-prop.ts create mode 100644 src/client/render/gl/debug/folder.ts create mode 100644 src/client/render/gl/debug/index.ts create mode 100644 src/client/render/gl/debug/layout.ts create mode 100644 src/client/render/gl/debug/props/color.ts create mode 100644 src/client/render/gl/debug/props/select.ts create mode 100644 src/client/render/gl/debug/props/slider.ts create mode 100644 src/client/render/gl/debug/props/toggle.ts create mode 100644 src/client/render/gl/debug/tree.ts create mode 100644 src/client/render/gl/debug/wiring.ts create mode 100644 src/client/render/gl/dynamic-buffer.ts create mode 100644 src/client/render/gl/events.ts create mode 100644 src/client/render/gl/game-view.ts create mode 100644 src/client/render/gl/index.ts create mode 100644 src/client/render/gl/keyboard-pan.ts create mode 100644 src/client/render/gl/map-interaction.ts create mode 100644 src/client/render/gl/passes/bar-pass.ts create mode 100644 src/client/render/gl/passes/border-compute-pass.ts create mode 100644 src/client/render/gl/passes/border-stamp-pass.ts create mode 100644 src/client/render/gl/passes/conquest-popup-pass.ts create mode 100644 src/client/render/gl/passes/coordinate-grid-pass.ts create mode 100644 src/client/render/gl/passes/crosshair-pass.ts create mode 100644 src/client/render/gl/passes/fallout-bloom-pass.ts create mode 100644 src/client/render/gl/passes/fallout-light-pass.ts create mode 100644 src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts create mode 100644 src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts create mode 100644 src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts create mode 100644 src/client/render/gl/passes/fx-pass/index.ts create mode 100644 src/client/render/gl/passes/lightmap-pass.ts create mode 100644 src/client/render/gl/passes/move-indicator-pass.ts create mode 100644 src/client/render/gl/passes/name-pass/atlas-data.ts create mode 100644 src/client/render/gl/passes/name-pass/data-textures.ts create mode 100644 src/client/render/gl/passes/name-pass/debug-program.ts create mode 100644 src/client/render/gl/passes/name-pass/icon-program.ts create mode 100644 src/client/render/gl/passes/name-pass/index.ts create mode 100644 src/client/render/gl/passes/name-pass/status-icon-program.ts create mode 100644 src/client/render/gl/passes/name-pass/text-layout.ts create mode 100644 src/client/render/gl/passes/name-pass/text-program.ts create mode 100644 src/client/render/gl/passes/name-pass/types.ts create mode 100644 src/client/render/gl/passes/night-composite-pass.ts create mode 100644 src/client/render/gl/passes/nuke-telegraph-pass.ts create mode 100644 src/client/render/gl/passes/nuke-trajectory-pass.ts create mode 100644 src/client/render/gl/passes/point-light-pass.ts create mode 100644 src/client/render/gl/passes/radial-menu-pass.ts create mode 100644 src/client/render/gl/passes/railroad-pass.ts create mode 100644 src/client/render/gl/passes/range-circle-pass.ts create mode 100644 src/client/render/gl/passes/sam-radius-pass.ts create mode 100644 src/client/render/gl/passes/selection-box-pass.ts create mode 100644 src/client/render/gl/passes/spawn-overlay-pass.ts create mode 100644 src/client/render/gl/passes/structure-level-pass.ts create mode 100644 src/client/render/gl/passes/structure-pass.ts create mode 100644 src/client/render/gl/passes/terrain-pass.ts create mode 100644 src/client/render/gl/passes/territory-pass.ts create mode 100644 src/client/render/gl/passes/trail-pass.ts create mode 100644 src/client/render/gl/passes/unit-pass.ts create mode 100644 src/client/render/gl/render-settings.json create mode 100644 src/client/render/gl/render-settings.ts create mode 100644 src/client/render/gl/renderer.ts create mode 100644 src/client/render/gl/settings-utils.ts create mode 100644 src/client/render/gl/shaders/bar/bar.frag.glsl create mode 100644 src/client/render/gl/shaders/bar/bar.vert.glsl create mode 100644 src/client/render/gl/shaders/border-compute/border-compute.frag.glsl create mode 100644 src/client/render/gl/shaders/conquest-popup/conquest-popup.frag.glsl create mode 100644 src/client/render/gl/shaders/conquest-popup/conquest-popup.vert.glsl create mode 100644 src/client/render/gl/shaders/crosshair/crosshair.frag.glsl create mode 100644 src/client/render/gl/shaders/crosshair/crosshair.vert.glsl create mode 100644 src/client/render/gl/shaders/day-night/border-stamp.frag.glsl create mode 100644 src/client/render/gl/shaders/day-night/border-stamp.vert.glsl create mode 100644 src/client/render/gl/shaders/day-night/composite.frag.glsl create mode 100644 src/client/render/gl/shaders/day-night/fallout-composite.frag.glsl create mode 100644 src/client/render/gl/shaders/day-night/fallout-composite.vert.glsl create mode 100644 src/client/render/gl/shaders/day-night/fallout-light.frag.glsl create mode 100644 src/client/render/gl/shaders/day-night/light.frag.glsl create mode 100644 src/client/render/gl/shaders/day-night/light.vert.glsl create mode 100644 src/client/render/gl/shaders/fallout-bloom/composite.frag.glsl create mode 100644 src/client/render/gl/shaders/fallout-bloom/composite.vert.glsl create mode 100644 src/client/render/gl/shaders/fallout-bloom/extract.frag.glsl create mode 100644 src/client/render/gl/shaders/fallout-bloom/heat-decay.frag.glsl create mode 100644 src/client/render/gl/shaders/fx/attack-ring.frag.glsl create mode 100644 src/client/render/gl/shaders/fx/attack-ring.vert.glsl create mode 100644 src/client/render/gl/shaders/fx/shockwave.frag.glsl create mode 100644 src/client/render/gl/shaders/fx/shockwave.vert.glsl create mode 100644 src/client/render/gl/shaders/fx/sprite.frag.glsl create mode 100644 src/client/render/gl/shaders/fx/sprite.vert.glsl create mode 100644 src/client/render/gl/shaders/glsl.d.ts create mode 100644 src/client/render/gl/shaders/grid/grid.frag.glsl create mode 100644 src/client/render/gl/shaders/map-overlay/overlay.vert.glsl create mode 100644 src/client/render/gl/shaders/map-overlay/territory.frag.glsl create mode 100644 src/client/render/gl/shaders/map-overlay/trail.frag.glsl create mode 100644 src/client/render/gl/shaders/move-indicator/move-indicator.frag.glsl create mode 100644 src/client/render/gl/shaders/move-indicator/move-indicator.vert.glsl create mode 100644 src/client/render/gl/shaders/name/debug-box.frag.glsl create mode 100644 src/client/render/gl/shaders/name/debug-box.vert.glsl create mode 100644 src/client/render/gl/shaders/name/icon.frag.glsl create mode 100644 src/client/render/gl/shaders/name/icon.vert.glsl create mode 100644 src/client/render/gl/shaders/name/name.frag.glsl create mode 100644 src/client/render/gl/shaders/name/name.vert.glsl create mode 100644 src/client/render/gl/shaders/name/status-icon.frag.glsl create mode 100644 src/client/render/gl/shaders/name/status-icon.vert.glsl create mode 100644 src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.frag.glsl create mode 100644 src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.vert.glsl create mode 100644 src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.frag.glsl create mode 100644 src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.vert.glsl create mode 100644 src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.frag.glsl create mode 100644 src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.vert.glsl create mode 100644 src/client/render/gl/shaders/radial-menu/arcs.frag.glsl create mode 100644 src/client/render/gl/shaders/radial-menu/arcs.vert.glsl create mode 100644 src/client/render/gl/shaders/radial-menu/icon.frag.glsl create mode 100644 src/client/render/gl/shaders/radial-menu/icon.vert.glsl create mode 100644 src/client/render/gl/shaders/railroad/railroad.frag.glsl create mode 100644 src/client/render/gl/shaders/range-circle/range-circle.frag.glsl create mode 100644 src/client/render/gl/shaders/range-circle/range-circle.vert.glsl create mode 100644 src/client/render/gl/shaders/sam-radius/sam-radius.frag.glsl create mode 100644 src/client/render/gl/shaders/sam-radius/sam-radius.vert.glsl create mode 100644 src/client/render/gl/shaders/selection-box/selection-box.frag.glsl create mode 100644 src/client/render/gl/shaders/selection-box/selection-box.vert.glsl create mode 100644 src/client/render/gl/shaders/shared/blur.frag.glsl create mode 100644 src/client/render/gl/shaders/shared/fullscreen-no-uv.vert.glsl create mode 100644 src/client/render/gl/shaders/shared/fullscreen.vert.glsl create mode 100644 src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl create mode 100644 src/client/render/gl/shaders/structure-level/structure-level.frag.glsl create mode 100644 src/client/render/gl/shaders/structure-level/structure-level.vert.glsl create mode 100644 src/client/render/gl/shaders/structure/structure.frag.glsl create mode 100644 src/client/render/gl/shaders/structure/structure.vert.glsl create mode 100644 src/client/render/gl/shaders/terrain/terrain.frag.glsl create mode 100644 src/client/render/gl/shaders/terrain/terrain.vert.glsl create mode 100644 src/client/render/gl/shaders/unit/unit.frag.glsl create mode 100644 src/client/render/gl/shaders/unit/unit.vert.glsl create mode 100644 src/client/render/gl/utils/affiliation.ts create mode 100644 src/client/render/gl/utils/color-utils.ts create mode 100644 src/client/render/gl/utils/gl-utils.ts create mode 100644 src/client/render/gl/utils/gpu-resources.ts create mode 100644 src/client/render/gl/utils/heat-manager.ts create mode 100644 src/client/render/gl/utils/nuke-trajectory.ts create mode 100644 src/client/render/gl/utils/tile-codec.ts create mode 100644 src/client/render/gl/vite-env.d.ts create mode 100644 src/client/render/types/frame-data.ts create mode 100644 src/client/render/types/frame-events.ts create mode 100644 src/client/render/types/frame-source.ts create mode 100644 src/client/render/types/game-updates.ts create mode 100644 src/client/render/types/game.ts create mode 100644 src/client/render/types/index.ts create mode 100644 src/client/render/types/renderer.ts create mode 100644 src/client/render/types/replay.ts create mode 100644 src/client/render/types/unit-type.ts diff --git a/src/client/render/frame/derive/alliance-clusters.ts b/src/client/render/frame/derive/alliance-clusters.ts new file mode 100644 index 0000000000..3c895ebcaa --- /dev/null +++ b/src/client/render/frame/derive/alliance-clusters.ts @@ -0,0 +1,44 @@ +import type { PlayerState } from "../../types"; + +/** + * Compute alliance clusters via union-find. + * Returns a map of `playerSmallID → clusterRootID`. + * Used by SAM radius pass to color allies as a group. + */ +export function computeAllianceClusters( + players: ReadonlyMap, +): Map { + const parent = new Map(); + + function find(x: number): number { + while (parent.get(x) !== x) { + const p = parent.get(x)!; + parent.set(x, parent.get(p)!); + x = p; + } + return x; + } + + function union(a: number, b: number): void { + const ra = find(a); + const rb = find(b); + if (ra !== rb) parent.set(rb, ra); + } + + for (const ps of players.values()) { + if (ps.smallID > 0) parent.set(ps.smallID, ps.smallID); + } + + for (const ps of players.values()) { + if (!ps.allies || ps.smallID <= 0) continue; + for (const allyID of ps.allies) { + if (parent.has(allyID)) union(ps.smallID, allyID); + } + } + + const result = new Map(); + for (const id of parent.keys()) { + result.set(id, find(id)); + } + return result; +} diff --git a/src/client/render/frame/derive/attack-rings.ts b/src/client/render/frame/derive/attack-rings.ts new file mode 100644 index 0000000000..fbe025839a --- /dev/null +++ b/src/client/render/frame/derive/attack-rings.ts @@ -0,0 +1,43 @@ +import type { AttackRingInput, UnitState } from "../../types"; +import { UT_TRANSPORT } from "../../types"; + +/** + * Extract attack ring indicators for transport ships with active targets. + * Optionally filter to a specific owner (live path filters to local player). + */ +export function extractAttackRings( + units: ReadonlyMap, + mapW: number, + ownerFilter?: number, +): AttackRingInput[] { + const rings: AttackRingInput[] = []; + for (const u of units.values()) { + if (u.unitType !== UT_TRANSPORT) continue; + if (u.targetTile === null || !u.isActive || u.retreating) continue; + if (ownerFilter !== undefined && u.ownerID !== ownerFilter) continue; + const t = u.targetTile; + rings.push({ x: t % mapW, y: (t - (t % mapW)) / mapW, unitId: u.id }); + } + return rings; +} + +/** + * Targeted variant — iterates only pre-classified transport IDs instead of all units. + * Used by the live path where UnitClassifier maintains the transport ID set. + */ +export function extractAttackRingsFromIds( + transportIds: readonly number[], + units: ReadonlyMap, + mapW: number, + ownerFilter?: number, +): AttackRingInput[] { + const rings: AttackRingInput[] = []; + for (const id of transportIds) { + const u = units.get(id); + if (!u || u.targetTile === null || !u.isActive || u.retreating) continue; + if (ownerFilter !== undefined && u.ownerID !== ownerFilter) continue; + const t = u.targetTile; + rings.push({ x: t % mapW, y: (t - (t % mapW)) / mapW, unitId: u.id }); + } + return rings; +} diff --git a/src/client/render/frame/derive/nuke-telegraphs.ts b/src/client/render/frame/derive/nuke-telegraphs.ts new file mode 100644 index 0000000000..5523efb16a --- /dev/null +++ b/src/client/render/frame/derive/nuke-telegraphs.ts @@ -0,0 +1,57 @@ +import type { NukeTelegraphData, UnitState } from "../../types"; +import { NUKE_MAGNITUDES } from "../../types"; + +/** + * Extract nuke telegraph circles for active nukes with targets. + * + * When `friendlyIDs` is provided, only nukes owned by those players are + * included (live game — you see your own + teammates' telegraphs). + * When omitted, all nukes are included (replay / spectator). + */ +export function extractNukeTelegraphs( + units: ReadonlyMap, + mapW: number, + friendlyIDs?: ReadonlySet, +): NukeTelegraphData[] { + const telegraphs: NukeTelegraphData[] = []; + for (const u of units.values()) { + if (u.targetTile === null || !u.isActive) continue; + if (friendlyIDs && !friendlyIDs.has(u.ownerID)) continue; + const mag = NUKE_MAGNITUDES[u.unitType]; + if (!mag) continue; + telegraphs.push({ + x: u.targetTile % mapW, + y: (u.targetTile - (u.targetTile % mapW)) / mapW, + innerRadius: mag.inner, + outerRadius: mag.outer, + }); + } + return telegraphs; +} + +/** + * Targeted variant — iterates only pre-classified nuke IDs instead of all units. + * Used by the live path where UnitClassifier maintains the nuke ID set. + */ +export function extractNukeTelegraphsFromIds( + nukeIds: readonly number[], + units: ReadonlyMap, + mapW: number, + friendlyIDs?: ReadonlySet, +): NukeTelegraphData[] { + const telegraphs: NukeTelegraphData[] = []; + for (const id of nukeIds) { + const u = units.get(id); + if (!u || u.targetTile === null || !u.isActive) continue; + if (friendlyIDs && !friendlyIDs.has(u.ownerID)) continue; + const mag = NUKE_MAGNITUDES[u.unitType]; + if (!mag) continue; + telegraphs.push({ + x: u.targetTile % mapW, + y: (u.targetTile - (u.targetTile % mapW)) / mapW, + innerRadius: mag.inner, + outerRadius: mag.outer, + }); + } + return telegraphs; +} diff --git a/src/client/render/frame/derive/player-status.ts b/src/client/render/frame/derive/player-status.ts new file mode 100644 index 0000000000..2a165e007f --- /dev/null +++ b/src/client/render/frame/derive/player-status.ts @@ -0,0 +1,74 @@ +import type { PlayerState, PlayerStatusData, UnitState } from "../../types"; +import { NUKE_TYPES, UT_MIRV_WARHEAD } from "../../types"; + +/** Unit types that indicate an active nuke is in flight. */ +const NUKE_ACTIVE_TYPES: ReadonlySet = new Set([ + ...NUKE_TYPES, + UT_MIRV_WARHEAD, +]); + +/** + * Compute per-player status flags for the name/status-icon pass. + * + * This is the replay-path version — no local player concept. + * All relative flags (alliance, allianceReq, target, embargo, nukeTargetsMe) + * are always false. The live path uses the shim's own computePlayerStatus + * which has local-player awareness. + */ +export function computePlayerStatus( + players: ReadonlyMap, + units: ReadonlyMap, +): Map { + const result = new Map(); + + // Nuke owners: players who have an active nuke in flight + const nukeOwners = new Set(); + for (const u of units.values()) { + if (u.isActive && NUKE_ACTIVE_TYPES.has(u.unitType)) { + nukeOwners.add(u.ownerID); + } + } + + // Crown: alive player with most tiles + let crownSmallID = -1; + let maxTiles = 0; + for (const ps of players.values()) { + if (!ps.isAlive) continue; + if (ps.tilesOwned > maxTiles) { + maxTiles = ps.tilesOwned; + crownSmallID = ps.smallID; + } + } + + for (const ps of players.values()) { + if (!ps.isAlive) continue; + const crown = ps.smallID === crownSmallID; + const traitor = ps.isTraitor; + const disconnected = ps.isDisconnected; + const traitorRemainingTicks = ps.traitorRemainingTicks; + const nukeActive = nukeOwners.has(ps.smallID); + + if ( + crown || + traitor || + disconnected || + traitorRemainingTicks > 0 || + nukeActive + ) { + result.set(ps.smallID, { + crown, + traitor, + disconnected, + alliance: false, + allianceReq: false, + target: false, + embargo: false, + nukeActive, + nukeTargetsMe: false, + traitorRemainingTicks, + allianceFraction: 0, + }); + } + } + return result; +} diff --git a/src/client/render/frame/derive/relation-matrix.ts b/src/client/render/frame/derive/relation-matrix.ts new file mode 100644 index 0000000000..f0eca51eb3 --- /dev/null +++ b/src/client/render/frame/derive/relation-matrix.ts @@ -0,0 +1,92 @@ +import type { PlayerState, PlayerStatic } from "../../types"; + +const RELATION_SIZE = 1024; +const RELATION_NEUTRAL = 0; +const RELATION_FRIENDLY = 1; +const RELATION_EMBARGO = 2; + +/** Reusable matrix buffer — one allocation, rewritten each frame. */ +const matrix = new Uint8Array(RELATION_SIZE * RELATION_SIZE); + +export interface RelationMatrixResult { + matrix: Uint8Array; + size: number; +} + +/** + * Build a relationship matrix from player alliance, embargo, and team data. + * Indexed by `[ownerA * size + ownerB]` → 0=neutral, 1=friendly, 2=embargo. + * Embargo overrides friendly (matching game priority). + * + * @param teams Optional smallID→team map. Same-team players are marked friendly. + */ +export function buildRelationMatrix( + players: ReadonlyMap, + teams?: ReadonlyMap, +): RelationMatrixResult { + matrix.fill(RELATION_NEUTRAL); + + // Teammates — mark same-team pairs as friendly (before embargoes, which override) + if (teams && teams.size > 0) { + const byTeam = new Map(); + for (const [sid, team] of teams) { + if (sid <= 0 || sid >= RELATION_SIZE) continue; + let bucket = byTeam.get(team); + if (!bucket) { + bucket = []; + byTeam.set(team, bucket); + } + bucket.push(sid); + } + for (const members of byTeam.values()) { + for (let i = 0; i < members.length; i++) { + for (let j = i + 1; j < members.length; j++) { + const a = members[i]!, + b = members[j]!; + matrix[a * RELATION_SIZE + b] = RELATION_FRIENDLY; + matrix[b * RELATION_SIZE + a] = RELATION_FRIENDLY; + } + } + } + } + + // Alliances + for (const ps of players.values()) { + const sid = ps.smallID; + if (sid <= 0 || sid >= RELATION_SIZE) continue; + + if (ps.allies) { + for (const allyID of ps.allies) { + if (allyID > 0 && allyID < RELATION_SIZE) { + const ab = sid * RELATION_SIZE + allyID; + const ba = allyID * RELATION_SIZE + sid; + if (matrix[ab]! < RELATION_FRIENDLY) matrix[ab] = RELATION_FRIENDLY; + if (matrix[ba]! < RELATION_FRIENDLY) matrix[ba] = RELATION_FRIENDLY; + } + } + } + + if (ps.embargoes) { + for (const eStr of ps.embargoes) { + const eID = parseInt(eStr, 10); + if (eID > 0 && eID < RELATION_SIZE) { + matrix[sid * RELATION_SIZE + eID] = RELATION_EMBARGO; + matrix[eID * RELATION_SIZE + sid] = RELATION_EMBARGO; + } + } + } + } + + return { matrix, size: RELATION_SIZE }; +} + +/** Build a smallID→team map from a player list. Skips players with no team. */ +export function buildTeamMap( + players: readonly PlayerStatic[], +): ReadonlyMap { + const m = new Map(); + for (const p of players) { + if (p.team !== null) m.set(p.smallID, p.team); + } + return m; +} diff --git a/src/client/render/frame/index.ts b/src/client/render/frame/index.ts new file mode 100644 index 0000000000..fcac131809 --- /dev/null +++ b/src/client/render/frame/index.ts @@ -0,0 +1,20 @@ +// Re-export the boundary contract type +export type { FrameData } from "../types"; + +// Shared derive functions +export { computeAllianceClusters } from "./derive/alliance-clusters"; +export { + extractAttackRings, + extractAttackRingsFromIds, +} from "./derive/attack-rings"; +export { + extractNukeTelegraphs, + extractNukeTelegraphsFromIds, +} from "./derive/nuke-telegraphs"; +export { computePlayerStatus } from "./derive/player-status"; +export { buildRelationMatrix, buildTeamMap } from "./derive/relation-matrix"; + +// Upload +export type { RelationMatrixResult } from "./derive/relation-matrix"; +export { uploadFrameData } from "./upload"; +export type { FrameUploadTarget, UploadOptions } from "./upload"; diff --git a/src/client/render/frame/railroad-cache.ts b/src/client/render/frame/railroad-cache.ts new file mode 100644 index 0000000000..803201eaf2 --- /dev/null +++ b/src/client/render/frame/railroad-cache.ts @@ -0,0 +1,273 @@ +/** + * RailroadCache — always-on accumulator for railroad events. + * + * The game doesn't expose current railroad state via any API — it only sends + * construction/destruction/snap delta events. This cache accumulates them + * every tick so consumers that start later can reconstruct the full set. + * + * Includes orientation computation, construction animation, and a per-tile + * Uint8Array ready for GPU upload. + * + * Ported verbatim from openfront-workspace/packages/shim/src/railroad-cache.ts; + * only imports changed (types come from src/core/game/GameUpdates instead of + * the shim's local types module). + */ + +import { + GameUpdateType, + GameUpdateViewData, + RailroadConstructionUpdate, + RailroadDestructionUpdate, + RailroadSnapUpdate, +} from "../../../core/game/GameUpdates"; + +// Regular enum (not const enum) for cross-package use. +export enum RailType { + VERTICAL, + HORIZONTAL, + TOP_LEFT, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_RIGHT, +} + +interface RailTile { + ref: number; + type: RailType; +} + +interface RailroadAnim { + tiles: RailTile[]; + headIndex: number; + tailIndex: number; + complete: boolean; +} + +const RAIL_INCREMENT = 3; + +// --------------------------------------------------------------------------- +// Orientation helpers +// --------------------------------------------------------------------------- + +function railExtremity(tile: number, next: number, w: number): RailType { + const dx = (next % w) - (tile % w); + const dy = (next - (next % w)) / w - (tile - (tile % w)) / w; + if (dx === 0) return RailType.VERTICAL; + if (dy === 0) return RailType.HORIZONTAL; + return RailType.VERTICAL; +} + +function railDirection( + prev: number, + cur: number, + next: number, + w: number, +): RailType { + const x1 = prev % w, + y1 = (prev - x1) / w; + const x2 = cur % w, + y2 = (cur - x2) / w; + const x3 = next % w, + y3 = (next - x3) / w; + const dx1 = x2 - x1, + dy1 = y2 - y1; + const dx2 = x3 - x2, + dy2 = y3 - y2; + if (dx1 === dx2 && dy1 === dy2) { + return dx1 !== 0 ? RailType.HORIZONTAL : RailType.VERTICAL; + } + if ((dx1 === 0 && dx2 !== 0) || (dx1 !== 0 && dx2 === 0)) { + if (dx1 === 0 && dx2 === 1 && dy1 === -1) return RailType.BOTTOM_RIGHT; + if (dx1 === 0 && dx2 === -1 && dy1 === -1) return RailType.BOTTOM_LEFT; + if (dx1 === 0 && dx2 === 1 && dy1 === 1) return RailType.TOP_RIGHT; + if (dx1 === 0 && dx2 === -1 && dy1 === 1) return RailType.TOP_LEFT; + if (dx1 === 1 && dx2 === 0 && dy2 === -1) return RailType.TOP_LEFT; + if (dx1 === -1 && dx2 === 0 && dy2 === -1) return RailType.TOP_RIGHT; + if (dx1 === 1 && dx2 === 0 && dy2 === 1) return RailType.BOTTOM_LEFT; + if (dx1 === -1 && dx2 === 0 && dy2 === 1) return RailType.BOTTOM_RIGHT; + } + return RailType.VERTICAL; +} + +function computeRailTiles(tileRefs: number[], w: number): RailTile[] { + if (tileRefs.length === 0) return []; + if (tileRefs.length === 1) + return [{ ref: tileRefs[0]!, type: RailType.VERTICAL }]; + const result: RailTile[] = []; + result.push({ + ref: tileRefs[0]!, + type: railExtremity(tileRefs[0]!, tileRefs[1]!, w), + }); + for (let i = 1; i < tileRefs.length - 1; i++) { + result.push({ + ref: tileRefs[i]!, + type: railDirection(tileRefs[i - 1]!, tileRefs[i]!, tileRefs[i + 1]!, w), + }); + } + const last = tileRefs.length - 1; + result.push({ + ref: tileRefs[last]!, + type: railExtremity(tileRefs[last]!, tileRefs[last - 1]!, w), + }); + return result; +} + +export class RailroadCache { + private mapW: number; + private anims = new Map(); + + /** + * Per-tile reference count. Multiple railroads can share tiles at junctions + * (near stations). A tile is only cleared from railroadState when its ref + * count drops to zero. + */ + private tileRefCount = new Map(); + + /** Per-tile railroad state (0=none, 1-6 = RailType+1). Ready for GPU upload. */ + readonly railroadState: Uint8Array; + + /** True if railroadState changed this tick. */ + railroadDirty = false; + + /** Tile refs revealed by animation this tick (for dust FX). */ + readonly revealedRailTiles: number[] = []; + + constructor(mapW: number, mapH: number) { + this.mapW = mapW; + this.railroadState = new Uint8Array(mapW * mapH); + } + + /** + * Process this tick's railroad events and advance animations. + * Event order matches the upstream game client: Construction → Snap → Destruction. + */ + apply(gu: GameUpdateViewData): void { + const constructs = (gu.updates[GameUpdateType.RailroadConstructionEvent] ?? + []) as RailroadConstructionUpdate[]; + for (const evt of constructs) this.addRailroad(evt.id, evt.tiles, false); + + const snaps = (gu.updates[GameUpdateType.RailroadSnapEvent] ?? + []) as RailroadSnapUpdate[]; + for (const evt of snaps) { + this.removeRailroad(evt.originalId); + this.addRailroad(evt.newId1, evt.tiles1, true); + this.addRailroad(evt.newId2, evt.tiles2, true); + } + + const destructs = (gu.updates[GameUpdateType.RailroadDestructionEvent] ?? + []) as RailroadDestructionUpdate[]; + for (const evt of destructs) this.removeRailroad(evt.id); + + this.tickAnimations(); + } + + /** Clear the dirty flag after the consumer has uploaded the state. */ + clearDirty(): void { + this.railroadDirty = false; + } + + /** Get raw tile refs for the given railroad IDs (for ghost manager overlap resolution). */ + getRailroadTileRefs(ids: number[]): number[] { + const tiles: number[] = []; + for (const id of ids) { + const anim = this.anims.get(id); + if (anim) for (const t of anim.tiles) tiles.push(t.ref); + } + return tiles; + } + + /** Read-only view of current railroads: id → raw tile refs. */ + getRailroads(): ReadonlyMap { + const result = new Map(); + for (const [id, anim] of this.anims) { + result.set( + id, + anim.tiles.map((t) => t.ref), + ); + } + return result; + } + + reset(): void { + this.anims.clear(); + this.tileRefCount.clear(); + this.railroadState.fill(0); + this.railroadDirty = false; + } + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + + private addRailroad(id: number, tileRefs: number[], complete: boolean): void { + const tiles = computeRailTiles(tileRefs, this.mapW); + const anim: RailroadAnim = { + tiles, + headIndex: complete ? tiles.length : 0, + tailIndex: complete ? 0 : tiles.length, + complete, + }; + this.anims.set(id, anim); + + // Increment ref counts for all tiles in this railroad + for (const rt of tiles) { + this.tileRefCount.set(rt.ref, (this.tileRefCount.get(rt.ref) ?? 0) + 1); + } + + if (complete) { + for (const rt of tiles) this.railroadState[rt.ref] = rt.type + 1; + this.railroadDirty = true; + } + } + + private removeRailroad(id: number): void { + const anim = this.anims.get(id); + if (!anim) return; + + // Decrement ref counts; only clear tiles whose count drops to zero + for (const rt of anim.tiles) { + const count = (this.tileRefCount.get(rt.ref) ?? 1) - 1; + if (count <= 0) { + this.tileRefCount.delete(rt.ref); + this.railroadState[rt.ref] = 0; + } else { + this.tileRefCount.set(rt.ref, count); + } + } + + this.anims.delete(id); + this.railroadDirty = true; + } + + private tickAnimations(): void { + this.revealedRailTiles.length = 0; + for (const anim of this.anims.values()) { + if (anim.complete) continue; + if (anim.tailIndex - anim.headIndex <= 2 * RAIL_INCREMENT) { + for (let i = anim.headIndex; i < anim.tailIndex; i++) { + const t = anim.tiles[i]!; + this.railroadState[t.ref] = t.type + 1; + this.revealedRailTiles.push(t.ref); + } + anim.headIndex = anim.tailIndex; + anim.complete = true; + this.railroadDirty = true; + } else { + for (let i = anim.headIndex; i < anim.headIndex + RAIL_INCREMENT; i++) { + const t = anim.tiles[i]!; + this.railroadState[t.ref] = t.type + 1; + this.revealedRailTiles.push(t.ref); + } + for (let i = anim.tailIndex - RAIL_INCREMENT; i < anim.tailIndex; i++) { + const t = anim.tiles[i]!; + this.railroadState[t.ref] = t.type + 1; + this.revealedRailTiles.push(t.ref); + } + anim.headIndex += RAIL_INCREMENT; + anim.tailIndex -= RAIL_INCREMENT; + if (anim.headIndex >= anim.tailIndex) anim.complete = true; + this.railroadDirty = true; + } + } + } +} diff --git a/src/client/render/frame/trail-manager.ts b/src/client/render/frame/trail-manager.ts new file mode 100644 index 0000000000..c9fd32d019 --- /dev/null +++ b/src/client/render/frame/trail-manager.ts @@ -0,0 +1,133 @@ +/** + * TrailManager — per-tile "last owner" stamp for trail rendering. + * + * Each tick, for each tracked unit, stamps tiles between lastPos and pos + * (bresenham) with the owner's smallID. When a unit dies its tiles are cleared, + * with overlapping tiles repainted from any surviving unit. + * + * Simpler than the original openfront-workspace TrailManager (no MotionPlanStore + * dependency). Since we run in the main thread reading GameView directly, we + * don't need plan-based reconstruction. + */ + +import type { UnitState } from "../types"; + +interface UnitTrail { + ownerID: number; + tiles: Set; + lastPosStamped: number; // tile ref of the last position we stamped +} + +export class TrailManager { + private readonly trailState: Uint8Array; + private readonly unitTrails = new Map(); + private readonly mapW: number; + + private _dirtyRowMin = Infinity; + private _dirtyRowMax = -1; + + constructor(mapW: number, mapH: number) { + this.mapW = mapW; + this.trailState = new Uint8Array(mapW * mapH); + } + + getTrailState(): Uint8Array { + return this.trailState; + } + + get dirtyRowMin(): number { + return this._dirtyRowMin; + } + get dirtyRowMax(): number { + return this._dirtyRowMax; + } + + clearDirtyRows(): void { + this._dirtyRowMin = Infinity; + this._dirtyRowMax = -1; + } + + reset(): void { + this.unitTrails.clear(); + this.trailState.fill(0); + this._dirtyRowMin = Infinity; + this._dirtyRowMax = -1; + } + + /** + * Update trails from the current unit set. Stamps tiles between lastPos and + * pos (bresenham) for each tracked unit, and clears tiles for units that + * have disappeared (overlapping tiles get repainted from survivors). + */ + update(units: Map, trackedIds: number[]): void { + this.clearDeadUnits(units); + for (const id of trackedIds) { + const unit = units.get(id); + if (!unit) continue; + let trail = this.unitTrails.get(id); + if (!trail) { + trail = { ownerID: unit.ownerID, tiles: new Set(), lastPosStamped: -1 }; + this.unitTrails.set(id, trail); + } + if (trail.lastPosStamped === -1) { + // First sighting — just stamp current pos + this.stamp(unit.pos, trail.ownerID); + trail.tiles.add(unit.pos); + trail.lastPosStamped = unit.pos; + } else if (trail.lastPosStamped !== unit.pos) { + this.bresenham(trail.lastPosStamped, unit.pos, trail); + trail.lastPosStamped = unit.pos; + } + } + } + + private clearDeadUnits(units: Map): void { + for (const [id, trail] of this.unitTrails) { + if (units.has(id)) continue; + const deadTiles = trail.tiles; + for (const ref of deadTiles) this.stamp(ref, 0); + this.unitTrails.delete(id); + // Repaint any tiles that overlap surviving trails + for (const other of this.unitTrails.values()) { + for (const ref of deadTiles) { + if (other.tiles.has(ref)) this.stamp(ref, other.ownerID); + } + } + } + } + + private stamp(ref: number, ownerID: number): void { + this.trailState[ref] = ownerID; + const row = (ref / this.mapW) | 0; + if (row < this._dirtyRowMin) this._dirtyRowMin = row; + if (row > this._dirtyRowMax) this._dirtyRowMax = row; + } + + private bresenham(from: number, to: number, trail: UnitTrail): void { + const w = this.mapW; + let x0 = from % w; + let y0 = (from - x0) / w; + const x1 = to % w; + const y1 = (to - x1) / w; + const dx = Math.abs(x1 - x0); + const dy = -Math.abs(y1 - y0); + const sx = x0 < x1 ? 1 : -1; + const sy = y0 < y1 ? 1 : -1; + let err = dx + dy; + for (;;) { + const ref = y0 * w + x0; + trail.tiles.add(ref); + this.stamp(ref, trail.ownerID); + if (x0 === x1 && y0 === y1) break; + const e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x0 += sx; + } + if (e2 <= dx) { + err += dx; + y0 += sy; + } + } + } +} diff --git a/src/client/render/frame/upload.ts b/src/client/render/frame/upload.ts new file mode 100644 index 0000000000..cc0774bebb --- /dev/null +++ b/src/client/render/frame/upload.ts @@ -0,0 +1,135 @@ +import type { + AttackRingInput, + BonusEvent, + ConquestFx, + DeadUnitFx, + FrameData, + NameEntry, + NukeTelegraphData, + PlayerState, + PlayerStatusData, + TilePair, + UnitState, +} from "../types"; + +/** + * Structural interface for the GPU view target. + * Satisfied by GameView through TypeScript structural typing. + */ +export interface FrameUploadTarget { + uploadTileAndTrailState(tileState: Uint16Array, trailState: Uint8Array): void; + uploadLiveDelta(tileState: Uint16Array, changedTiles: TilePair[]): void; + uploadLiveTrailDelta( + trailState: Uint8Array, + dirtyRowMin: number, + dirtyRowMax: number, + ): void; + applyFullTiles(tileState: Uint16Array, trailState: Uint8Array): void; + applyDelta(changedTiles: TilePair[], trailState: Uint8Array): void; + uploadRailroadState(data: Uint8Array): void; + applyRailroadDust(tileRefs: number[]): void; + updateUnits(units: ReadonlyMap, gameTick: number): void; + updateStructures(units: ReadonlyMap): void; + applyDeadUnits(deadUnits: DeadUnitFx[]): void; + applyConquestEvents(events: ConquestFx[]): void; + applyBonusEvents(events: BonusEvent[]): void; + updateAttackRings(rings: AttackRingInput[]): void; + updateNukeTelegraphs(data: NukeTelegraphData[]): void; + updateNames( + names: ReadonlyMap, + players: ReadonlyMap, + snap: boolean, + statusData?: ReadonlyMap, + ): void; + updateRelations(data: Uint8Array, size: number): void; + setSAMAllianceClusters(clusters: ReadonlyMap): void; +} + +export interface UploadOptions { + /** Snap name positions instantly (seek mode). Default: false. */ + snap?: boolean; + /** Skip tile upload — caller already handled tiles (e.g. seek with bloom reset). */ + skipTileUpload?: boolean; +} + +/** + * Upload a FrameData snapshot to the GPU view. + * + * Handles tile upload mode switching, all view update calls, and conditional + * railroad/ephemeral uploads. The FrameData itself carries semantic differences + * (seek sets deadUnits=[], conquestEvents=[] etc.) — this function is a + * straightforward dispatch loop. + */ +export function uploadFrameData( + view: FrameUploadTarget, + frame: FrameData, + opts?: UploadOptions, +): void { + const snap = opts?.snap ?? false; + const skipTileUpload = opts?.skipTileUpload ?? false; + + // --- Tiles + Trails --- + // Live mode: changedTiles[] means "only these tiles changed" (empty = nothing changed, skip upload). + // changedTiles null/undefined means "no delta info" (first tick — full upload needed). + // Copy mode: changedTiles[] = delta playback, null = full seek. + if (!skipTileUpload) { + if (frame.tileMode === "live" && frame.changedTiles) { + // Live delta path — tiles and trails uploaded independently + if (frame.changedTiles.length > 0) { + view.uploadLiveDelta(frame.tileState, frame.changedTiles); + } + // Trail dirty rows come from TrailManager, independent of tile deltas + if (frame.trailDirtyRowMax >= 0) { + view.uploadLiveTrailDelta( + frame.trailState, + frame.trailDirtyRowMin, + frame.trailDirtyRowMax, + ); + } + } else if (frame.tileMode === "live") { + view.uploadTileAndTrailState(frame.tileState, frame.trailState); + } else if (!frame.changedTiles) { + view.applyFullTiles(frame.tileState, frame.trailState); + } else { + view.applyDelta(frame.changedTiles, frame.trailState); + } + } + + // --- Railroads --- + if (frame.railroadDirty) { + view.uploadRailroadState(frame.railroadState); + if (frame.revealedRailTiles.length > 0) { + view.applyRailroadDust(frame.revealedRailTiles); + } + } + + // --- Units + structures --- + view.updateUnits(frame.units, frame.tick); + if (frame.structuresDirty) { + view.updateStructures(frame.units); + } + + // --- Ephemeral effects --- + if (frame.events.deadUnits.length > 0) { + view.applyDeadUnits(frame.events.deadUnits); + } + if (frame.events.conquestEvents.length > 0) { + view.applyConquestEvents(frame.events.conquestEvents); + } + if (frame.events.bonusEvents.length > 0) { + view.applyBonusEvents(frame.events.bonusEvents); + } + + // --- Attack rings + nuke telegraphs --- + view.updateAttackRings(frame.attackRings); + view.updateNukeTelegraphs(frame.nukeTelegraphs); + + // --- Names + player status --- + view.updateNames(frame.names, frame.players, snap, frame.playerStatus); + + // --- Relations --- + view.updateRelations(frame.relationMatrix, frame.relationSize); + + // --- Alliance clusters (SAM pass) --- + view.setSAMAllianceClusters(frame.allianceClusters); +} diff --git a/src/client/render/game-constants.ts b/src/client/render/game-constants.ts new file mode 100644 index 0000000000..51f5e65e30 --- /dev/null +++ b/src/client/render/game-constants.ts @@ -0,0 +1,163 @@ +/** + * game-constants.ts — Upstream game facts replicated in the renderer/shim. + * + * All values here are sourced from upstream game code. When upstream changes, + * audit this file first. + * + * Primary sources: + * - vendor/openfront/src/core/configuration/DefaultConfig.ts (DefaultConfig, DefaultServerConfig) + * - vendor/openfront/src/client/graphics/layers/FxLayer.ts (visual-only constants) + */ + +import { + UT_ATOM_BOMB, + UT_CITY, + UT_DEFENSE_POST, + UT_FACTORY, + UT_HYDROGEN_BOMB, + UT_MIRV_WARHEAD, + UT_MISSILE_SILO, + UT_PORT, + UT_SAM_LAUNCHER, +} from "./types"; + +// --------------------------------------------------------------------------- +// Tick timing +// --------------------------------------------------------------------------- + +/** + * Milliseconds per game tick. + * Source: DefaultServerConfig.turnIntervalMs() → return 100 + */ +export const MS_PER_TICK = 100; + +// --------------------------------------------------------------------------- +// Unit health +// --------------------------------------------------------------------------- + +/** + * Maximum health for a Warship unit. + * Source: DefaultConfig.unitInfo(UnitType.Warship) → { maxHealth: 1000 } + */ +export const WARSHIP_MAX_HEALTH = 1000; + +// --------------------------------------------------------------------------- +// Construction durations (ticks) +// --------------------------------------------------------------------------- + +/** + * How many ticks each structure type takes to finish construction. + * Source: DefaultConfig.unitInfo(type).constructionDuration (non-instantBuild path): + * case UnitType.City: constructionDuration: 2 * 10 + * case UnitType.Port: constructionDuration: 2 * 10 + * case UnitType.Factory: constructionDuration: 2 * 10 + * case UnitType.DefensePost: constructionDuration: 5 * 10 + * case UnitType.MissileSilo: constructionDuration: 10 * 10 + * case UnitType.SAMLauncher: constructionDuration: 30 * 10 + */ +export const CONSTRUCTION_DURATIONS: Readonly> = { + [UT_CITY]: 2 * 10, + [UT_PORT]: 2 * 10, + [UT_FACTORY]: 2 * 10, + [UT_DEFENSE_POST]: 5 * 10, + [UT_MISSILE_SILO]: 10 * 10, + [UT_SAM_LAUNCHER]: 30 * 10, +}; + +// --------------------------------------------------------------------------- +// Missile cooldowns (ticks) +// --------------------------------------------------------------------------- + +/** + * Ticks for a SAM Launcher to reload one missile. + * Source: DefaultConfig.SAMCooldown() → return 120 + * NOTE: different from SiloCooldown — do not conflate. + */ +export const SAM_COOLDOWN_TICKS = 120; + +/** + * Ticks for a Missile Silo to reload one missile. + * Source: DefaultConfig.SiloCooldown() → return 75 + */ +export const SILO_COOLDOWN_TICKS = 75; + +// --------------------------------------------------------------------------- +// Deletion mark duration (ticks) +// --------------------------------------------------------------------------- + +/** + * How many ticks a structure remains in the "marked for deletion" state. + * Source: DefaultConfig.deletionMarkDuration() → return 30 * 10 + */ +export const DELETION_MARK_DURATION = 30 * 10; + +// --------------------------------------------------------------------------- +// Nuke explosion visual radii (tiles) +// --------------------------------------------------------------------------- + +/** + * Visual explosion radius (tiles) for each nuke type, used for shockwave and + * debris scatter sizing. + * + * Source: FxLayer.ts, inside the unit-death event handler: + * case UnitType.AtomBomb: this.onNukeEvent(unit, 70) + * case UnitType.MIRVWarhead: this.onNukeEvent(unit, 70) + * case UnitType.HydrogenBomb: this.onNukeEvent(unit, 160) + * + * Note: these are visual-only radii. The gameplay damage radii are separate + * and come from DefaultConfig.nukeMagnitudes() → { inner, outer }. + */ +export const NUKE_EXPLOSION_RADII: Readonly> = { + [UT_ATOM_BOMB]: 70, + [UT_HYDROGEN_BOMB]: 160, + [UT_MIRV_WARHEAD]: 70, +}; + +// --------------------------------------------------------------------------- +// SAM range formula +// --------------------------------------------------------------------------- + +/** + * SAM Launcher coverage radius in tiles at a given upgrade level. + * Source: DefaultConfig.samRange(level): + * return this.maxSamRange() - 480 / (level + 5) + * where maxSamRange() → return 150 + */ +export function samRange(level: number): number { + return 150 - 480 / (level + 5); +} + +// --------------------------------------------------------------------------- +// Missile readiness formula +// --------------------------------------------------------------------------- + +/** + * Fractional missile readiness [0, 1] for a Silo or SAM Launcher. + * Returns 1.0 when fully loaded, 0.0 when completely empty with no partial reload. + * + * Source: adapted from upstream readiness display logic (UILayer / FxLayer). + * Uses per-type cooldown: SAMCooldown() = 120, SiloCooldown() = 75. + */ +export function missileReadiness( + unitType: string, + level: number, + missileTimerQueue: number[], + gameTick: number, +): number { + const cooldown = + unitType === UT_SAM_LAUNCHER ? SAM_COOLDOWN_TICKS : SILO_COOLDOWN_TICKS; + const maxMissiles = level; + const reloading = missileTimerQueue.length; + if (reloading === 0) return 1; + + const ready = maxMissiles - reloading; + if (ready === 0 && maxMissiles > 1) return 0; + + let readiness = ready / maxMissiles; + for (const timer of missileTimerQueue) { + const progress = gameTick - timer; + const ratio = progress / cooldown; + readiness += ratio / maxMissiles; + } + return Math.max(0, Math.min(1, readiness)); +} diff --git a/src/client/render/gl/assets/MissileSiloIconWhite.svg b/src/client/render/gl/assets/MissileSiloIconWhite.svg new file mode 100644 index 0000000000..4235be74e7 --- /dev/null +++ b/src/client/render/gl/assets/MissileSiloIconWhite.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + diff --git a/src/client/render/gl/assets/emoji-atlas-meta.json b/src/client/render/gl/assets/emoji-atlas-meta.json new file mode 100644 index 0000000000..ddd8fdbff0 --- /dev/null +++ b/src/client/render/gl/assets/emoji-atlas-meta.json @@ -0,0 +1,68 @@ +{ + "width": 1280, + "height": 768, + "cellSize": 128, + "cols": 10, + "emojis": { + "😀": 0, + "😊": 1, + "🥰": 2, + "😇": 3, + "😎": 4, + "😞": 5, + "🥺": 6, + "😭": 7, + "😱": 8, + "😡": 9, + "😈": 10, + "🤡": 11, + "🥱": 12, + "🫡": 13, + "🖕": 14, + "👋": 15, + "👏": 16, + "✋": 17, + "🙏": 18, + "💪": 19, + "👍": 20, + "👎": 21, + "🫴": 22, + "🤌": 23, + "🤦‍♂️": 24, + "🤝": 25, + "🆘": 26, + "🕊️": 27, + "🏳️": 28, + "⌛": 29, + "🔥": 30, + "💥": 31, + "💀": 32, + "☢️": 33, + "⚠️": 34, + "↖️": 35, + "⬆️": 36, + "↗️": 37, + "👑": 38, + "🥇": 39, + "⬅️": 40, + "🎯": 41, + "➡️": 42, + "🥈": 43, + "🥉": 44, + "↙️": 45, + "⬇️": 46, + "↘️": 47, + "❤️": 48, + "💔": 49, + "💰": 50, + "⚓": 51, + "⛵": 52, + "🏡": 53, + "🛡️": 54, + "🏭": 55, + "🚂": 56, + "❓": 57, + "🐔": 58, + "🐀": 59 + } +} diff --git a/src/client/render/gl/assets/emoji-atlas.png b/src/client/render/gl/assets/emoji-atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..14969caa8d5da0f14cfc0bf3c0b0f856ab09c7b7 GIT binary patch literal 219817 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"-`E*Yro!HbT_eecITnug4ICyO19A= z`q3)Rw>kKFB))N(cC#({0vXhZ_@MC4sY?OUW|@pp#w+ra2FrieAvkiRBlK4Z|2!@D z=y~>-~Q_76w8CRl27JH0laqtM7MMt z;ee8BtZ?E_=B)T%qiJufD*UFvo4k)ZW@m{hyU^K--GH25g^S-|_m^+ZuyY^^?ST`Tt@|YqH*7=={)cGpbq$EnT zSlIGXeAYKiV*4zjVbCewCKwTZx3Lpt@y{&8(@Lb;;o3uj3`}&g(%L`PP|!@ub>m=u z*_Ibd&5hYHl8)?Q1zf73M}IYGmYM}u&64`q_p===b7GHS?OIm^ynPM!izNW5?{#D$j2gTTCip+9>roQTe$? ziy<_t&~CxVH%|eMsW7I~Ri~w*_R}o%wN+U1rzI>rHZImiXyGh*`LoIja`$qieuOT& zatTI>K8?b+}2 zyy0yZ!mM=6t})o4C_+N(-_?86+)d1kpP` z;(MAdkx$=H=vjQG^aG1*?au+!efJcqx{P-s9^2@%w}?V=fM$i0P2Cl~{+iI3z5R&| z`Wgm;wS9Cc5YL~i69Cn9_+n;!z^6EFaRZ@=K;BgKW&{Y}kN+_3DE-BJi^=0Djwl)W z$-mYK`DB@-I+d5iIb!XTll#+ktq9-AVC;^(&Vg=|K)rlE6vfLZfZO7!hSdH=2z+4- zkLbv%X$!vz*2J(?UFk6Xx^IF{o0!kKzh@KvuZ{dhZOoGWM2ru{^mpV41Eg_)9mO45 zog!R|F~V>%Zh3H9VW5Km!Hl`so5=RYHNu&5JH&@5={AZUVI3?$uolZQP$GwtKB*Av%NTO4@)qndpV?MdkrFqQd zwMg;A3g$KHPEV4g<487XekQFWtHZOes!S|~jvx)ZSTtJ@G7FRe|FZv%eEV2L6&jy*QKb}~9l}zr4Uwd5{{w1HC{F$8Sq{| z{aiud^+<7`#d_~RxJ{p&#j3q$Xw)77tMUmNh2F(Q%`>u~ubR!&kpptmTExQd2ie zyVEP9SbS@i$tD8czK`*#Nx?FY)JD6s${~n!tV3SPGyfwJNf>@>FR|CPJ*cQC*eENE z<)iz(0!eL3pJxBmAj=93iV} z-|D&39S}z7M;(`2$4GnPX+h7rHmnG>s>$AGZ(U&EB}_#+7=n_?zt*YOkjy9FOaVq{ zCr9f-;tVW7%i_+Xxe0`u+=cVDN77#W-dA${XmiNV2j0zwCGa-P8x`Hm7RPqXMh2 zNd}CV+;jBmynBJO-YZAMdhO%9@Pm8t1gRdS)0**goJXyFC5=kSDV-qbE^uaUHt?YM zmf-{#OXafuHGxwsW;_SZI0@7v4vx?mA)9dzbua7&o*QUV>p+sl%U?%XxCk!HOWsWuSkRAYiy@2<#BaUKm+jO!hy(X7)X2Qx}<(dCFK zs$=dX0$6n$#hD$qRv*?cz9f@sxE3WuKmRu6AE_-=2hd`1bVu59=z$Fr&0P3xfcv8g zUl9Vb9&P5#J8yj#OFN6-HV6Ho_YV(7*#zd=IUM0FR8QP-cEe9CNN(dPC(Ur!39k?m?AFHUSy)1FY6M zZK(9b&GdW?QFD=$Cw+7KQhd_t^ZmxM8TWDsJV0h^$lEJZ;
  • H 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 o@nb#_O7DC}lacCx)$m~e0*w0sl{4kwC z_;$Xr_W3s5k3;M_njueKLh@-;*P|`vTS_)vpgau^;3< zW12(OZPmrEKQ8o^Fl(4S+a->qP>i_TCm+QVyJA}&N&H^EP$mD#N55&Pc zj5Zj~#*^f<>@3_Lm=M3WH|e)|TV;D?tYs=jNqiS8Q6tk6KeTnf#bj5N43w8wK3z+x z_)jz`;ou=bO=4CfB5})~gG}hk0Q?G;)iQAAzBh@aqA+5;pGuBku3}^DCQ5|4?sf}L9L13Oj=}tnA>@M;p2y7AOf>A+ z5Zxv~qPxv9S4N1nJ0vJv7T7YwKMi6*y9o&E+c+)L+Gw(%4{j^ZIOO#nmKa<6^M_YL zDPhYk8u3ay6-qG+-#!Haz>Pm@!py=#b`;^9e=y^$RA}op)G>B{kZh##={bh%4wr^s zigN95^6|fj@9-HPV?rHtL?^qd@*h!o=;=;(gug(;p#J#hZ&$1=!W(%YugHI@Dy<5m z6v0PvN1f7&{hO5#hr)aFaa6&9`nHvb zGYthI-X#pO27^-#0$D>yi>IBPrYwrX(|xeP9=(>*1NCBlyHSR(Sgt-p*e{kE(EK5|Zk|@nJEx^#Znv7h)bD zT1B+ilY!e2H89)`v(Ugk3eWu-066^T{?KzEzX44*r#uH zt7c)eSb;pkdlDI<76DYp9gsuS2-{x~&;I0=(Ljhs9c3<+A}Be@6wQ^owm$Lux5s(c zg}Sjoq{WJu;7hSO*xt&!LOHs#C*fJLh2C&xjiub4L>A9ftzhz0?PaRAl6LzWf)o(# z?VW|Xr!_yXV``7vwY`=sCk*dAQQDI#6YBtlN3IGjl_+fK$gFERUEbo39UPkdOk~uR zg?*;#88H#b%6oZ@T*{sOkRaz5&@&D68+hW6P$!Rkiw$Zj6!4+vx$DFmul(j9zm&pz zyK4ID>E|eKizaQWR(;q$u9r$*(i&SX1zbDKJ6wAxJ<>YPw4*f~2-&rm@S<7Q!*56z zCquEGS4!KoBCPGny}dbrHKBa~X=aNQIo)S?I@Lpa3enF~61MX5S16h+IwGK*F9#ug z;=ppN70^dzUi{F;n{xGm-GO=Q%@wiyJPI_WlOoXW!_5Q{!*8p}IKsqU-lG}814X0u z1MDcXg#eg9aMjweowdR9L79h~?e;PFGUO`bGonT8vITr4M{?!0exlPGo%?IE%vL4BHK5duv<8I2+TeE<;l?ZH|O^Nr# zmD!$4){4T$X2Sf*2L68MH{@FVzm!tEK+R14?N9A*BJp@5@T*6SfSK!wt?)xSKYdui z(g&Jk^`}lTjvNf^GnzHf`Y9VAYDxpl{r3LnMO(f@Gnqu@9L)9jCo4tV*6Hsb6h8|I z9S;q}2i#T4?C3Wryhef`Z;n3k*{3hv9X-_bB0t?OB6`#zGT}Iq4R1O#DA!Mh1G7l- zC7gJe?=fpaL_FwLu4u1XFs|ml5b|jwP>hlcuvLeD`dwXkwa{XN(vt|l{7{Kh@OqL- z1PPe52zyD1plQV|4=>h;U zJ5AoJgUg1*nkk4D0})KhX(V%7yh2oa+Sa$#_J46;$Kxhw9t+pZ=`@FYBn-B&b39e9 zGe9Pyrw%o^pHx37=`OcL16s!8a>#)EQl*5hw*(!|EnQiJ@C7$i7>{&;f!Eihu%wdO z+U9_R8**3dT@OPY9rrdv9AR7XqvCQd6rHJw4sCsV0|R|CO&#*Rj|dm2cKa?mLg3;r zm=Mz;t+AL>134(v3>--GoJPTGKhmt@XIo$^{^*1VB><;p_FjG7xW!mq*>NEXeMjwy z2*vbZh>{2CWetu{;q+f|RYZRCm3;cs;KzBfx^ACWx7<;6$#T+LL9?STI>2|NeLr-% z+)gYW+gx4|fo2+;e~dehoOnBcAciOynR&b@HVc9kF~^!Yy!w5Y16!ctwb58g@Qpw9 z&K$F1BF`TQQfnemh9g+v`<0ZCl5QVXPm$#Ye+v?zwILKjZdpD!nE73^GQI5|ERT#= zz%ohW{u*UwwD|S7Bik#kPu`e7;Y`G%^lQ=gyFSQ<_5D*=C}}Z2D26FiST%kup79mp zrQtUEiyNC6XzOb|4IwN-4&g_aTzh;nN430WPrc0+?SdV@gqLL*_-tSD-S`@pGcTg# zdg2&{=#+|a!1$y-hWfU6F~CYfDJ2tx5TYUuQuSc+fQR;1%%gcOQvCyAW_mq;O}ty^ zjbPT0(wHYg#_)+R(YT|n9_G4vvqmZ_DQ_ummmIvE* zywJx&3}0ROZ6wX8cNdX1^Cab!Pb6XJCkm;Eo)%OoVJyu$7p+AkXcN=u9@}zpGZ#Cn z)pQbqo!=-+ES*QsdSg^5v15+B0&$$QeixhTN&+xe=(&Ia*}7`f$qPi^07}X!O)&Yh z&$`jos7-h)OEk-1sPOFcKPLgH$gQBgyB0p2LxOH5npiLMVc9)P?JY@|gg3o?ztm66 z@Ws*g_vglzJMF|t7D0KfQN;L~m+f&_+>#}&H$ge#wL^{3k_J4G2&Pq#w=DkC;bAjN zUlu2tMCuu=?tx_Wy{krU9KyQi@ii5!Bn>uWFdRVea?1Jip?L?Dq^-?Zd@3)Q*C}Va zJyC_)Rqh}lHy!GWiHRBTn;p>p48&Ezzm5RamUoUa*G1%HP~C$@l&i;MO+?`Ujw6`k z;xwYd3}(L|bifZEPi8CeM^B~1#wjb597yIw@DU*UCLSTzyF>lara;{B4D(Uadc;T$ z4SlW*hAdTRd~YxEmNlk9?VV<@R?&M^B)LEjszg{V@NP#V9i{43MpRJP z1b#UF=(xi>k<1A3@5x__BL|Ska{lW)) z?%AlOIM%fRt;z=5bx`g@aIugxOu#p09lzQhbNvhv0oNY=4Z1P{(LOcKWB&wEiVhSp zLsUvS*Ixk>iDb<&Nn~&LYFWV$YN!|o098=WG-LonCP&YgX`GL+98rW{g%VZgCDi9^ zk~D5HAqU0Wf(1TxA0VJZwR8tTCdmi;*%wAJcccUiis-f+pOrnIjn;3-VxvKe5OZS6 z#49_@EkO0b9U0%J6!cmqh3XK&a{S}?gT^aJq9c|7l#m?^uVH4Fc4yK9@V%4#wh)!@ zKZ(2ZivSe%I!~a6kAwg?1@^tMfbxD85)jOm8DQ&b<7j<0TI)6pk9jLF)Fd{lq2!Dt zm~kk~AN%I)7w#QpBa5^PB_?B?!JRZBeb7p=tn9ZC^nCTj6|f$kjRo6D2o5NE1<__T zVy*{R%+OIfSgFOWEMaN1vl75>_&>cwF6Q-CDKbt!nwWUS!{qOl|>b*Nkz-22c zP^$YGQ0M!O3Y&?WIt1k zf6}{BAplBIksx`y?hAKahFtZxjUXsuIiw2nsSYrci%lbT;o;BxmOG!?d7QC-dq_bS zb$;$rZaL;ImEa%G#F5%GVAz?|9CbObc!k(x|c7cR6z~H4wgs+;+O(CL3XzT^xCR=h!nJ$8!nui9I z*RM`m+(wrJaLsN>{vJe3M3{#o2&z-M^8(PuL4~HcNI4y0aiBcY_~C@=qe&)=i?(1t(i-zjgV;M;G^y3He zPMe?b9R9laDG)tR|CE(GJ6iYA){!_z9#}!Hu?u02{m=9vdWp%IBGF1f>Z=h%`)K~( zKXpIqw4U%lkOy!6o|pfVy85wJ@$dm59jufF@W|!s{P&Mf>@9#GyA}&)L|^;}0RGBv z;x2j7(S*pcHL-Y2u7T_5EdAqmJ@R6qcZ~>jQQH-wT3qD^*fJKeVl~n7K?4jK0OX_o zd7lkjHa@r<3Ehz90cnfVexB%(p4$d9ed@i<-#_O@pSv#{2cn_6-ppG z{RbX=jjZHiDjV?N~rtXb&~) zT6R%NQU8Xzujr7M#Tpi05RdpSgS?0iA66<5MS=}nVEF0T;SGjtJ1XF-Duc+broM{9 zdwjK&oM;4cWlm)6eXRn96%7vKh1yu)C6c4x9~F7Y6xlakU{)8oc@dl3)%2+fzKNkn zX6(y4j#fHW<2jwXBpxxd<;=bKQSOaee%cEt?iZOMCbcP$fr_GBIkdKi#jLC0eUuOb z!np>-*m~HTwxeEZWIvj#$Iqwi5y0>dx|eh&2%2pQMV^6pVqmX>o#0Hz_rkKG<1 zPV}F#pa9E=c%!%v>>tyPwR_-%Q$at9d3^T`^1}tFV{5Sh`)Aj^TZzBOQ*vdDbf@F{ zKIj4_G^Dog#GV;6C|2?!z4?aOfO+{X2oWTpioi55V znMz`AJI61gzfB)Mn8uKiTg>D@f+O4%I~D%w8?DjYIPRzA+XM6J{#gaQtZK5W$LlZZ z{_mKRMN!Ci3}MNBxQGI#b$*#7y*@Vi8%koOd|3Q_{ILt7Z!cFC*e?CEURorC0zF;0 z_!O?AUAo-$3SgULs^)tAUr;3iS`&FR5dNSY9q!jTb+)O^ESn-CDD`Y5D?4ueBY zJ`Ri^jM|6cXtJox2)9Ol<`;OKdW;30S5aIt=d;VK$S!xr2@$Ucv8f(YPg4gVH7tBi zPUN+$$r3K8SC3vTD>LWelJxyRbUqocCh4TD4SR7x8scC`s-GUI2U3+@^o9s)qmoBZ z^8PV{#oHl8uj`B|RC-?3BN(cA{IB@c$z|Hp`M+J~nj(QgJ}(#XUmLgblLbB$-vD)^ z!q^XHcK^XMM!>6b#h~!!SG7Z9`{n}fXlZ3<8%-vwZc#0dg71HxuJQ zCI}??&waCeIernuzsWzUg_yNoHcLmcG0Q^!d@r%EM))hR{wV!~(vY)K;%~W)5vLnv zH01<#{!NPw!qpJB@$+I<=P{om({ToMjc{9XT2|$>ks>YruNR;fpojG4pJzpQu;)55 zuP-i7Zx@VgFVl-vvUB`Zl#ZWZF&Xe+B6N}xJA63N4U%)gBYxu%Y+&n(c^{3A#GpH> zfF()q9rBSB9deKSRQ^@{_sBpzOs=2@Z$-P&pZH4rSQnOe>5L}dr-$%a3a3JV!T|g$ z9TmdqmSZ;ku?`uT>RFKUih#(s13tAMXHuM~bU?zIUwr*?uPcT_>dy3$puSd@UG351 zr1;^Fl<1Mov9nc@TQMzaLloFRtnA#1SH8*aOz!I+aR*iuM8wk35*cqPCI-8|671^A zEYreLp9!Or>XB}6yWM@jhxS9JM%=MfEsQ(0i9hU1R|xzl)$ zdb>2Fs;LgREpCzf6!=icLb2V4=M<|&960Ik@}AX@w({X_>HG5L&^Q_XbgezaoUBy3 zC=QXL+0UgCG=oO6gU^tY+*uOrDO7H7u-xMQh~ zo1;s#7L<^dR<}G1m{dVxPk1nfY#{xf@3Q86>Uz8u^HDC$NemXhAM#-bEJ)4GNQ)Ov zUlo#D4H;E<9weu^N*6+bS$=?FCXR?J{}+9cpmIsX5>TyG_as~4L;sZmXmY4Cs=OOFMMmU<(4i3Kn|5@-e&Z$4ffXK@ zKVKyTK}f^4ym)7RGsjYVPs}?Rp=*_aAl{9^1mPp+m!viJzWNflJ3~S6i}CZG+TC~a zaBloc`=b?MsIpH@4Sa_EPSEnNKuPm zy)xwTBYrOLUrFECKXHpCD9jy4wo*`VI;K@_4M!k#g6?R71fs4jI$J@W^a&7tMXEZ? zjLJ8?;wVEJt{F7~l{>{D4t;3*k@org4KwaV$cKL7QH2+`vilD zXxaZktLfdS4BsXcXj8`Y7rm4n2`i!7s(Y z)C2uCjKc~{bwOq2B+n`mizw_RaDXA}h~d5iFhsnOj*m3G9V--3JREg5r`Br0cdP9N zPV@-gzZlh!Rh0zNMbXkxB)Y}kYusy2j|mC$)4khks9x%;uK4q4NjWMA!=+8YEd8>A z49vX>lL1jj5>Fg)t1DZ(Vt&d@?_WyCiQG;Q^lN4Czg-y~Hn`(M4=)6GSXRi?8d0BW zAM8E>D8gj@3(%UDLUSHOqk+lildZp3gjyUU5HTE|U5et*+?^3lBOwy^9xO@d|K@2B zPhw;BNvK%ib{9%hQ>Kiq8N&-{I3@ZB4P7eUNnw~f72 z4OkUWFJHPLX0WT@kbnF|0C)bC%!YyFW8fKPgzBvF?G~m%V=c94IvcQzNS|m3QM}AI zjXpt;F*}+yBxjbk-WqXC9C)hBEYqP8_*WDd7+@86_g}7)1gOjYMRR^7 zTB(as=oc%GGB=|hlQWFD;7XkCM*`$(uW%zOS<1b*S3Q3>hcqi}%%TQ6&~==V}K zzP9tGT8QbYMlX&LLg~mfkvP#;ijh~2-JsIrAk3o6;eH|X z83l9&<#j3*YA1ikJ_A@kJ4EY zIp+}$vMfs;1f~R^9AT|4sv^f|+YfF1#u zaFpxG-VifBA|VF6_WJ|C4HCd+{@n0u~abNxCbu}(fE>Mrug8B zqDpr>QLZgH9s0^iR}4pGwDt~|c)U^)q~RQ=$3F;`LEU_|mt*8gZfCTp8ap|)1hNmG z!1fY(TA&<}{M}n9eegsGvsk99<~PiscGTODMQzsYxCdqUzw}Z*dgB%JXI&`U)c{RB z>+bcu*%YE4aRuC)z0+1zbMtKd4>d^vpONJzUoA&I4ygQUJ8UIJZ23W>n(Skl7>nmv z$toIMF9WJwpD6cfN7&ID-?$LS+Tw$WwMLE9(tECDYe}yfWlyAy!e6M>;w-D5zm?eE z5ftXc@w`mh?$FVynh4Pv*+2$gCd%L-Ls9?LWo5+L%GgUODt^#P3A6R$D~lITa*^A6 zLR_TV^#|CV*K_twZ9^s*iX6k4>&lqN7>Ij#Xk7+W(wYPt%FpR~Q>0PR74c;GW%;5ANe{&wL$$)P|4>ca1(gRB& z>auN}WY;2F5=eurQwweuEr8uJ@$eL8(7fuz(6pqC zvG39f^RUB(=v@^e$@uP}aC`I$ZRb~!Wi&3}DO$jQ`~p@oL=6hqrj%n@EpYY8d+@98 zS)QR?d1-YuMy7DSHW&!$O>;3ZY-sQ(zB4ELNITTHVzgb(3Fz61Ibpy4Br)>pzUBP! z`KXzLxqrEl6$dBFNIL-N^}BAmVghw8m(_f_Dm$}G6BekEy(`95Eqn<0c^GB*AN)j2 zT)_F`OAOyG~?YKqCJqr(Kf^dL8D%cbtCHC$_#7nBJG z2k#-74a*tbL=Fa?DBFDg6o0U?_bdFzjt@JEMZR}m&D`x<52@L07ov5#f>cmk+i4zl z+J2c{2c?geCsR9laLA)*9|a6UgXqzz3t=tdpux#U8K7a8Cs&>v^7i0v^!YFGCu7~`pEVA^`x4!)&76BX$g3} zac7!X_lUwAdHo9HQuH_6{|ntP2Kbt5KJ9#2wK36G;P7Iz077w>FTWBK^9Rzop8Yqg z>Yrq9)Q1sukcA$JkNDY)yY=f!E?l>f6Km^xez*0LN^H>R??(iU3`yAK3UtnAG1N)V zUNI?RzMSxO@(|ZE$@RZVVkBTb@T3LohHzTIaZ|=YZw|Z(GKxSyXfn^(mSgD(lMVYb z;yGaIU)bcX1-@q)&olc@Aqonlh??7GqbYjg(4LBGgC~JEv_!}&Zlz{LYTX^ibdhl= z|4WqX&xk!bsqkXjgBx=AuK3d&y*f@geoq-f~U#A0i)|d2&`pwWpml&14gazcNOG9<*4^Mi)zKcsBM5yg(LxhJ<6&O%M9U zlaC&Cz=ZsAbm-~XXuf;7F?EITpMR&H8hbe~W9P00ufy|r)jXqfTVG4(f{dVK=vUp( z1ZP@r6u0yfU;UO9uB~{nsffHX*2gSP<7yHo7T~=3-m$YS{kG6A5nSz z1#o;+1-E6Cjct%!g&sU{f<0AR)l91&u=;Z!(YxMC1Q&tR4F-3_VXjwRf{)C?Ifm?F z82Evc)^|2|*g3=P)Iov%w#v(YHGfyt*W);q?8&dq?2(ESBPVBwQBxJ2gbG%nLTzT7 z#syKK)pGH!Er#)d1wQnzucr;sj`xWU%WM8)rKy16O)K>Z;ca4tvV{dh*VyOBjtO-` z5;0N{_qGdrgG!RJ6YQ{r`qeOIRYQ4w1XScHv~Y)Lq4o%;B}Z`PmzY5YJdoHELR1?_ ztY^7l7Boogk$V-c3bJ6tW>G06hV25j$9%v{tG=*{Ser5nM~~GAVzHd0^e*&# znQwc*j`!AO4m3Bt&>Xq*CMe5ifPp!eAtykVw#xJ~v%^F@jl0@N+P1@M5X$cUJf1(B z;meS5Xe^?j|9i=jNQI(E(rg#Hi7&tD1XdEB^arY~FAe>~DANBy9IuJjAX~9MoM0akwNxWpT=iqR{~n%opBV;_?m2aib^Q zl-r`5Xm%FsN!d0f|7-+a`6f7WFJb=&u>}~h5!mBf!`*Vj`~l*F*v!Qk_X+sww~S^& z#rS+^Hm=NO?&Uh}IvDAz{ox%as_Qj?J<|il!?3HiFr%8;thTUII%Vk>&O&Sj)~|i3 zbQ;B*N2c#1p1r)4KsvOYdF9V{>31PIawPDU6uH=CD_GG4dmTi0VK}ZR7-)7&o=Y$X zEiFGvsKAcXO?DtQpEWu^I97E);S zn9yON+m%EQ9Tlsa4PfzcyL=Kb542+kpm>eN$m@HYDo{4l8UIWk)$XnRQs`;jhut=)xs{OtD}^l~A4l}wQh%2b{xRg4z8 z&C4vC4b|igER@)_*3Uyng0|m2rBRUZh&MnBelzUj_G!cGGoF9~b)s#geEly1TgKhz z?lSalL5JZ)IFZdeVMLpehyH(CFF=&DN8pFw?{0r?oWZAhiiXgC&cxM+R`}x5?%}Fq z_Ad6s_&f?drk46eAw%*tvd*h)sa(r{an^YEdi+?elE0y$0rjDk^gqcV z06E0*mnLNf0m!b;Z_P_;d&StB5`N*2ylR|=!@Dt%nx&?T#w>G38cx4jK%o9v@tFHB zekWW33EGUaWaQ4YJ7e)EsYnIZz3K_MWO^hZ4j$+fg{0foL6|lVEpH8S3DN<0yRb&e zBnhUVsi@h}Nl}uMvT8`|xXqy)t$dLWa!gVYG+>R`98!>{9ozrsFDJO4&2%p-gnzX| z8BL4~Kx4w;+9f1K@>xIPNE@ehf-rL6yg3ZiRutowX>o0jt$7LaKZ%uOnF#+7P{Do6 z4OBcQ>~wmGdrq-9?U?@UEa+`D-^*bVULqQIkm~ZYZM){-O*y7!vf@MmT!JqN)i(%9l$66l6?;UkiNI-B^BD0 zggiKp3lpUQf-^!fU(>A}B-Eb&o*sJF5dGagGFaF)E{df*dW5 zzK-3Zp_9(BYt>px0gz!}auoSImk(PvPc~v48ecFQYx-Ft)64CYVk5kW;`6&__mpOD zpo&hk+Gih&6>7TGG3gKw+fgkNVKw_5!5yvTTQ&6HdoFjgB-C$hEisVolfHw_ z4I9zXRL#3a*}@H#j4yH9sFlG(tVf-FHHy_SQ9fczF1~<6zL+ffJON^waa?F*>-&D%&ColFxIc(jsX|1S9 z{z0#y8Me>Cxqsy5KqeZq;XKiKHL^EH+)cNlYjDTrNAi72c@QlY{QV~ogm~2O5!H0Y zV~-PJ!RUWn#l84QEz>)0cp^N0*1HR!JOx5n$So+N261z|{b+=*mkX>aUSI(+Buu_2 z(Ayzrla}B2*9uB>f2wm*@~a@xMt-^=Yo71!gqsoG z-l{*A+fE#S(YN2YP=G5YcwZ-r39nDwtvIv+R|MeYx8$#LA6X?cr+dfLZ2obpHJBcGP2SW?afj!!!yCpFHfnnj$Inp1$VA^8a!51JF>Q< zU1b?>myK1t%8jsUo#CGsLJ9CGh2C3mlw5Mh=wkp^DLd9jpvI1K*Fdwl`Vyiv{<~wA zCLhE9=fcr}%rvOXK+sMwH0k9E&HGD=q5HFr;?jaAJ{zA-QOYzGP4uequ|4)b0BpxD zSr7QkYw(pk>`v-C$jk<+n5xu|l&@)hG{Ue+ZF-xYD`}kiJ0Z}~!GM=ok<-*(pBn|0 z-|5Kt5@()r4HwueRFsjw{IzBQS6!(Vi%G6gu>?G7+($YqES51h48|DDCW#l^E!}lB zvnM0=e5M4*jDJj(A3-%CzK$w-(bUyQXEnKRt0##@;C1g6f+G@6ylxJa8QSUZh)i>r zTK2xLy7`2tc9FUGKWrb8R!@d{^TT*|cpN|OV}^uX(lB5bs|&FbtYMYr8+9b{Mp4|m zNRJ+gH2TmpJYe_3m5@)Q_^RHf%CB~QY1tM4BpKiq9~_UC=jLvyv`<-+%Iu;-#Fqii z?OiA{U=EszR0lqTc{gleEIf$tS3DOMKiI)df1b?#!KeK#6^!`7qX90l-e}wQRl3RU z;PqieKBATzy3L?C&DI6brUx43T!}*!=+VA*&UIP&W&BLKBkkz|3QSaCF&$al0_Gx$ zyq;lp_f)xvfO#y+j8Z1a@D5FJm38%R17!@?`?Q0kgwD&gBYom&dwk=I)(~B65 zrkDWkQ6yS-iMBujNVtceZa|I5ijL@kLx4TA=bdh3DV8HR!w^%eYhW<~#>)F|r z9CiepI9igB9oJX&LrpVKtf99v**=vutk~p^w1xF0-vRd1i?(`Gp29R>KA z7tB|GoA6aDZdt5765h&>6K;P>aMu77IQfZ_*wIk{Hnvyu>o-dXM?qlo&7fa!<(s5t zlL7huQSg5PH$e#8hm|^HiCe5D3;JuK5h=yxfRX;^LO{5rR*!{G7DDX!7+p>K(=IG% zJp+FZ#Qwf)2N66YpUppwUoA>q<)3!ngXh1}6!2;ojs4wQfKU(px%*b$}F#$#3E zlx{9Y)bJK>ANA*#R|pC2A6y@6Al7TY7rZ=Q9KAoGH)&UV@Cnv*`h0Wo_X(6Jg+=sw zKziQH3e`s@(UGHDUVY3XY29C%?t2uqf^gY0SGLM68H-=qSIpQs{63X+tlQk~#Te41 zHFY?2A=*Ed8THcbtZh*)#CTFV2&cr}-DjBRZmBs6#&%@UzHr|^xN46ALxMHlYTv%I z<_45Jg_g7!kD8U{N|%Or%OyOGFfXqpiGf;RUse#10f{~9Kv9{+k%Cly6oAL~iw^{C zAJYSwA`!_Cmk{F}G361wk4VJ1PspLi>NCL|mf~3#vP{Qkn4l{LG;xQ}zOdh{ECwB) zk)RD_QlpM4=r`$Tc{x-VCgsCYc$^8o5J6PM&ZiG-SgoyW3cbcx;g)pU0>6|mt=HO> z_GWY@(p^el6`2WIt=PGqkGJc-~$8o&7x&*%5p$xny!>i0blCXIwJg3q2C+|9ai?r6ox z+=ZKSF5Vc}_89o!&9h@=m9;eN>fa$F!x_Z`wv%rJ#T?p@e)K{w-U%inGZ{cgj*>GE zF`F5i9SOs5D#$M zkJ#1614M*{+k+NtELg*bQvPL8rm?N5p8^;Yi`LjtD#tIXdAIY|}PvB(YjE49gjs(B&MMs7z zBoFwF+M75WAfX1$|236r_=&0~t5mJOd%brF^Kf`|rpUUAZbRWa*(xH*Q2s)c9~tuv z`57$|ppG$oEmTCU9V}r+fZww_=%+-n-1G9DOX<1LWm1jfL+cUD*@hv5XGswnZB+!U zSigDk$kWQPu|C51+FS!q*&um_>a#`UBaxX6{DSF;OuLJKahi*iJ^PGE;5y~8`v^YZ z`GlVQaBuAN;6L0UJ!13f7K9m=!*i8QYQ&o4Bs6{&&27;;pjFg9aiTRRQp-e*{j9_b z3q4U%;o@z%eQ-{WJy&$)Q5na|UyHs$X|X@TE6r1toPAyenZ(89WzG0;ov37VAz0kQ zKl_AIDr;b_2|MxIK>jsfTvU+S)QBQ5GqH5TqyMcL!l<{0kyyEuTwfyI>QZob*FLQG zR$Et6;C|ig<1+8=t@&?{MagSvM zMq9IW2DDr1Kt4`QOR^}Sgl=;(%a9b(sd94bT5w~zzU1;^G*F+;!PiD^D<-o_W`Ip)!KN3&`?Z!KqcUGKd9Y*G&5!{0=V^C za%T<0sMY&ikFV0K`x^1goS}->T;m_fK09P6x`zZRR4w_Y5IqW=?;afmx2Ky->mAeO zeVpigVkv`E{K+B^bf%+Fo+L9V9Y)r&C&9gowhJH;w zwlV(=zqNAfa?Mhw;AC#gZ8FcV=UujuDJvYH%>#OA z;a-w&E>x>OKL0n7(*U+fM)fYJw%N-v2>x79q6jv`$t+96Jfx<9OmMIh6BG|qCqYzA zS5T?%B#F5JJ)Jlx+|wL|Z6nLdQWm`vP`1@p*c3k@2quM`ho+T^<}C?cj}KuvGG)V1 z$CS~QNKK>ICo@Qh>nUYl_ls??vn()SgL1v6_j`IdPDF4)u8sMjM*ngnzJ$N7&qoJt zJ(VrZoY$xS$hs=5#N+oUb%19ZnvyQ1ep=#i2Y^mY6>tn>u)H^dy&iuFF)Mdw}h-v6#dkbT8x zOd#jS{?DlD!{5C>K_&x=FT!Y^ZMW4Z;B9-YQox8UgEY1K1*>zKpXPf6d*IQPw5W1; z1*nFPdoNbAU{gY15Ny>;_zZ}}+~}PNQ_`f)In<(Aq1N0OF8F;itwY~ZRQu#ccxZ9k zGa^CIcij_P0L|fDeI77UK`f4}n8Ob}GyZA$hP3ogcEh{(gy<#(D$MiNf8JV3b;Mtd z#MJEm%rF&2a;g&nC|T9gg8iAaPO@=Y8hc}?m2*)vRfX}bLS;EjlGMqz>phHpK#keD zoxwVM_;)vCUXIiJjb5ub)g?0D4}z=$pQNp3kNz0@(Y$|nh&5Tved;t_rnq`Semu{X za0-FS>xn8n`X>hCo}(bG?d1`Ba6P1%wGrP(#J6E=eg9B7H0kv7E%|dseQ1Pk9K$0} z@q-n6*ZY(Y_Ch@nzS_+Msq;g5=Sxe+43RD5ScN|i{f^j6KZ1oG+ne9WkD_;#2s{g- z40Hc>30HZ|Vk~;Cgk4>w?yMGH@AfD+RhR`s zw@&Ie|Ixl7=xM64qrkc1IKP_URJzKou<@!#l1e3AaC+%6wzJaoKiC0+btuD?9)pZn z;58^b-5q9F2UQnXfD%XU)z23~kcRz_F#I-b#Z{A+!tfVYdgD#9CT)YgbX1d4vsT2$ z2^fjNG^njh1gL<VH61y4`U*o@OOHGk@GWwsT`vSv56%R*yVmw z{ehF;IiU1vIsf#*^0*{a2u9c%>a!iBNh&=d^ej=zly*&;AzScTD1P_M@U1!q39^ny z5q?d>6-7L$SfNvXvbM#IB)1*VC z^r>sYSn`1)@KcaGWfi{X`JWe!jY(KHUCUiOh#G0@3F0XxKM#}?NtS!)PJ3WX^(*9V z<-pA@S)#4l*E^@){*@hpavr_V3UTaO5YqtE$Q1wgLY?&m#9}5&jKWd6txH`7&9 zp_64o8qe;hl9l7e>mn*(iLg!P{%-|;qell7^iuTXl{yvko=aon;Gv)&9)}>%XsMb> z>-gHsI!+8;zWwTmNpk4{qt1wnT+S2d1%{9WgF2Ke3SXgAUa0Nr_*ujK_uX zv&g{aS48ecyck*ERqchsZX3A79`P|d`Hc5sxusR=XWbxkP>Z$~(6O)D5(V%YKra+e z%a_Td@5FO=l`O15HA(Mpn!#RE+WXFI#kXL^Aon?;#4c)etgH)>$JdWWFv81(07z?G zs8!g?tcm>BfSrp4Oi4m!l(3aesl>2QQ~20V$FyNf$`49SRYm14?XC}QAKsUmE&RYT zJ9H5aK8)~0f}$S_-91cNDob^;0D`SNS=6L-1E^;Ho5t0gSlrWI1&{7k$0Isi**Y5O z&jdwhpT=|^qu#|4e7?1Qv3h34hNUz>xVg~Kt*$E+=w$60H8@0Tj)XT@M3frt_c(!n z#rl0|oO?m{OHd_*6F=#EX)quRKSS!_9zA>h1BUQ?+-Lkz=_6TQhkWrJQIFOdjBU)i zO@3=EPpRqFC@4TH;TSWF1)Ps&P)jZ?;M`BTeoc9{v^Usw_8d6YvH9ln$+nsyiS_C^ zXaSKNl;RNsS*@c#c{B2i4|Xa4|993gZT*Tmc*&(q&8uimln2muyg+*@AJrS~>5ant8Ry-h#tHXMD&h@BR zYgYHpVBalp(lH|QSXe-@^Mg7DV=(*E_wSo*zq;6>to}TGX!okjws2J_icP-3v(nzq-yI3kEjNJg@cuh=u zv)->C>p#TwBbMH#{G=PlAxQ?9+&j(rr+MKQcch%|#X*OpW%h6tzWdISC5aA|Qr={T z>#evqi?Uwt5KZKKD7@p@;Q3Ev+t}Ya(Qm2HGH`!>0JN>4(|XbOZN6{h z8;>v&KIK+bnyY%_=4yF42X)32R#{%&8ii)ltM^ldk5Do<^oPV(52IZ07_pF?#Kns6YfZTM~^4!3JKfS?nh3xa#M1bQFi2; zR;k43|Cl#$vXnW}MJ*JY5GH+%y|#uIugo_q4&n^%R*rRK%vp*ThhIR~@SdDJ?qzH1 zPck>LhxaA8e#{)SJyUv0QkbnNu))CHlfz@2RHo=qJeH%aESEMcgm)fy=G$11!qz2F z6mvq4g$!MfU96A`dKAB!nyZ0v6ZU%uSw>7u35kk$yfz~v6|A(lKRniqHHA5tsBmna zW&p=dUnzhI$7-6qWx)+xQC+Ud=jwX!Jd4)Nfyhqnlp1oZI$DF<_gZJTEZ1AFhKnYT z=cd}!jm~i2CN_c6u!M|^V_#}DIUslwDW=ue5V9(wi`l5a$1V*^{RTxn_<_qJNB6uL z+$YAJp>voc3Q<`(Or;dfi#n`M8}gE9`55^50<*}WmF#u>Cv1S+Tn{~V?{Yr7 ziK@)|z#k}kwv9V*02*yQ$2A4E$@FFQJz=%Rk8@3dgym^<*&UX{_QsrX*#e%V7kI?$ zV!TC1qd@qH&$%mFWuB=tpg8?7#Dld%phRE0D+wZ27+%Dq(A0`zvgB;a7T^M%x za(4>=<%1cWDvkGn)e4BZh-O+C1s#Z)VJXviY$T1^a8#3{O#)W0hu#Soq61No5~Ygiz|BE(9tl4Sy1-hbbe3dU5E=Kj zJMbf&6Hcp78VRjA{f?H|TXu7mFf>|c^bCHCn^LGRLmO^J%i+8}QC(EexnJdo2o}h$B8%f0{ z`g6BiS?(#di#!?M-#ggvgwoy!vLHV%p{^=$$A~iS!~VS;5dl{^wrb|Lc+HkXi9rf9 zzY?he^HaXZYD>w+*`^MG(Yo(3Hb&Gieg(N8>FRs(})NtNT<@B(riMyrMo1gk?y-Z=RNm+?jNvzdDhJH z%zR_Ut1r`Qh@4|Be zzehMWD^o?cF(BDres=V3-i(hgm|?U0{!;3Isyg!p1xE7H#ENo!qD)7v@3$%a|0QO; zI;`LzO)bjn5`7f_3G2YYpdl^2Jb4DoC%R7uYFPnWa8)6OHI$`36&4=(pSL7FCQsEC zb}?}rwjL)k8z@+cos=S!L9Vppb>1a{i&{6%E%u(zjc@mHYv@>zxN2rcq)1=kGH&R@ zidZD=8Z`39M@}H9QY{-e!hKr&$ExZWf7_@&=HLyU$jk@4Ots;CmnZZlqFOA*hUd|t zsU~cCt3Atpy}x4WgYdq1rTmNa);;#KQf0Rg37HEmg=TwasLWSUKz2SOF*GuXl4~X} z`)3zBl&)1|vYtx#l$#NJ1u>0k5Jrq^5GF_vv&eeUetsVl0{@h4w;rL)KJnvVd00I? z|B1b3jsV#@%`|%${Z&?TmWBK>I%F z$B!Exi>lH$6(Ipc(_!nYM>VVk>y(G;b=8LpVliD&{~?i>0S1u0@LYP)FM5^X40t~Z z2a&6C4Z^#rl5>cehBE3(|o+x~lch~4w&#mew!#hRvsljxpB#RRWVuaMNC{?&JrCQ-%+}7qaVeoP;?1ST z9W5+HkOetWh);t|g!?LFk%S&K0j4$Mq{P(G^PAAF!Y|W&;%a*!Yj1j zz}vJKy;>@Y&lSm1;ls*IdeK>VZt+)7b$%Tq)IGm~nrZb*$_Y|&HwjC?<@{W@Yg4*_ zwS6@7mg5o*N8y~!7j=T)A?E{la?3>1aD~Mwq7_;#M>pQq+8sG>5lP}1Y9r*G%DQpP zwzt|<4H=|JWo6FRl)om}rkP?k{(pa_>qUR5-voF8-qlJX#$&L5K_sN7o-~cPAs9yj zFStgeI(Dja1A-GCbxSNMIdo>`aF!SoiUdY*CxaAvqoOxWy;lUD@S`fmMd4q6uGwf| zU9%ps!eEe=4M`mhtubhos?R-iMN!9?ji;LRwi|SZ>XL@0SHRw0KS5a*`q`ELKxJ%NS3Z4c(7+Kcr@tBOwtANu?%MpI5_6Za^j zjZ|&!Ye%98r5AT|^L)!wEL=yy%_lK^(pm)UDviiBKP9i zDY=!@+w_;5l8mz@%}bU+0R|dxR*cuFTAPA9B!5b;?$a!c+1Ep;%y?DS+Ng>VU?hv$ zSAz?Q$$sx`lo~Jtkmz%&NRehByaCCyIq#r7$!M}cD(n5=DBZ#R)0c`57gGv z)|}MQopXM5e!$efJl#PB?ge@}`X7-juOYo@%4~HzQ^6W@2#QjiMPb&*7SEK|aC#Mk z-2!)UfP>QBT7CJh{Mqcn@q3Z&)}Upv$|}Pb>pIHPdV+}Y6C1_DTC=}Q6O^2x3|f0C zH*;U5xw1xf!D#q@>prQ1U}YhMxv4~crr=#-eKG5pVTt7J%F33YoIOo- zQ%DDiV1dl(yiA>R)!(;d4iet}#^# zY2TRKxm97TzBZf3bzI9k0(Je0r}kiKo~(fr-7W}+z)zBodq$u0qxHlVDB#Fe>*7wqP7 z4%6ORY|djf1i6K`+e(ge2g<1QG80=v6d!5)jnr0xL*l#OJ=xU3QUg;DTZ0Q&jg2xu z5{{T#ny=BbrBBs-D1S-rW!bik{|<-nDUl$fJW+0SuwQ5JdIs5zk&!J>yUhovHgUN| z$2xTURE)O@a4zrwS79u7ho*;+NEtL@D(h+|!b%>mbT@a}KVQ7)>Z8@fHgW3G!uWdl zh5Mwj>3SZvT6)&WKpBEVk{p~L#MLqHp8EVFax<|dDmFl2FmfZ(HQLt!TWw4H6*|9Tc9i@Hn(YZ3DRLUcQVlvgsJo4hKsN;-mQgF!k2%2S~butHiBgbpZoH--%WYE|2Y9ZF_55#Sb)-rkMy0iiKKzf4oo)8k&9~78i zO%k}mdFQ#^RO^+v*sjkWKdFd121q3#kr$PT=mDg!PtLX_uIm^Ix`_~88Z`g{l*ury z3QOvo@Y>&dNxj7RtIDhYZ{s)a3@g~61C?C2V}RjNA(D4f5D+yQP)g6hlvakR9@TCN z=BIj+b^925NAZ_&A<~s(r0+cgF?PS9JXnXXf%2bMLZ^@6Ed;*I%eeg$b;P4 z_jk4ndcG@5>S=y`C?s;*3ot4*{&EtqBXjqsn8(E8P>YFqk8TI!$pzRo|7$L$q9(~_ z;nB>ILX4}7%#0iDy*HSx4IglNfS9aiVZO+>Nkz3YW40^3HPzI7QT~;R`G5RS!0Ovu zwMSCEt>s7FYdqjQ0iyTx7mExggw%>I7&nQrs89A81^58Xsd^(-=C}(Tq>%Irb~@tU?q?32IRja|My@8h=Qk zUj?0VySJWTtD{@4#7b=?6$NE+qDA@|=x_a8r`dDd84;iR+#}e^J1X|1K zS>)7fu3#+bBKO<+FyZ^N;m7^tlcNidHeYl>%vC`H^Ccj=6cS})P0NGf8v5evIRKrU z$J#ZJj7y@1x}z{7)6eePI{PCF{zhX23|oA+>c;FA7x=1jIn8b7-R>FnNW*o8eH7T% z^-kO1si-q&5EyIXdck?)PCcim7q%x6LNI%y;5egwWQSM{r_|m}e*7PATE@-~D2*wN zQ3uVxu!pC-+`tPpfv>3f^CMy4{p%jGuD+N|`#dTjc3L1F=Ez?co4-GSwR9h*4EBTO zIKa2ZV8@aXlD7-A$GeJnF>++63YUnznpF>=tdycC+=P#JYie5+W6eN83gb8Af_-FU+{Vns(t) z%V*FXvX8FFd^hx9bQ>DqWj(oQzbCk3wI*!r`xA|Cx!YBo*+M zAbjoH6o5dxDj|Syo|FmOm#eGOG`Esv05Za0MZ?Li@gz}yElhty2P5fL*=npz5c8SI z62FD!5T=rubkkav4W?DMra8gv?Qwtk=+Utkx(Cqh<1C%XurOGqNTL5O`jPzh9pe(D zJgnB^z~!&1n~VT$u7jjy#yW<@{nv*t1NYs$=mCir8(Tf?83AWaT%JN?>?0DCd}NAu&}7ap@gUsjjcf!7hl&e#6*YJFcqxim?Ngx)5>0G#*}^KcGy zb#Xx%=YlJ8D=v@e!h>Er2eG7HHnlYigiD;^=5yKdzAWg{NVwLaJsI<*!}l8TTtWTN zA<2+PwVTS{kI?k^5<1`AdFfbR2d*hf_bGQWVRhUN&bLOHV^z`U=*crdY0k<;fQgUY ze--KnRlRWkFq+vBBLvf0d4r_94et#?+j!AzGDK`dOW9j zbmOO)bvv3ZvO`=BpR1x>D@)NQX0TaN)>P}w%CtHCucmN<1Zb}YuheLU-7c;I%otO?J*OQb!23+D!5uf;5 zlYM({+I`AbH8?*zG`N()F-iPBWZ_xFs}2z?cBbRI_rUhLIC8-njJh$BGt@i7O+=R@#+)1kt! z_mfo(zTYm#O*Zbl4@gR>6^EuZX0zYdPQU0n(ed-FtLKjtx_2weB+{RBl5R5)uN^29 zW=Qy|f%aWqBX3tJqL?$@+fSXWl9Sk@6U|KRDy=FrIeCU;8yp`;|QUhfqP!@J;Ull|mo;y+woB-kL z{>MDpwd{Tr#oYLfz@P_ZNq>FLo$JH>9W}IV{DB$h8t(DM&huJlTQ_#BLzl6G_hM+| zV|lz&t8pnY{AA32HPrmqxQ*uxKGng0_H)`R8Tty}d{OPvkF5#p9cx5v|19%C)2~#> z_3dSLgM-!rU>YE>E9jzQW=8HeDCvmKfA4p6W;8id=w?KavhZi{ag(OIO9-0gE}(uL zd0YM^BFLM}ML_iUyA99MaKLx~l2s1sT~EKPg=g~1Y`>wytCO0Mzhy!$22}`aVn3c9`14|^Bz(X8J;kLFmU@SUm|ccT2233bLEky_%Z9Ol z!jXy7XT^qAGPI?)UkG)m{3+hgj->M-&7U?JD4dA;8N-^B1PXYM^cb|Q@S)J42IOb| zOBioZWIhJfoS_EF5F5{vK#=OsArzoxMb(iqxd}3_6{g1rMr{%3t6Mr_sZ_#GYutOo zdOwK%GCnmU&BfFCCqnpb4U;ai@pVM}x~1_C&DbPMa!*EyJ5WQhx94J_?b2n@=jDHb zPlU2f;u==e{9vccL>Kbq8sJQkc>wTRDdsb|EKukMk=*X)kG%aPi@5ruDL8AB%ii^5 zgh4Mut3~W-CTF2vhe=4(tQ?~YZxx$+*L+6_jOfP10+~kI8dguWUWidH;A0ltvS;_w z=5j$&ZHG~oQC0-lu`E`iOM;?q&fQIlAf`Sm*@fk5%dz?A2ZB>PxuH+A#Jy(aYV6Mq zFAXBsR;L1f`Ad8P>$i6XKUePLC(TQ3K1kjMDNSzVjM_WyP(*t5@VD4}b?fvOXn&UX zf#%KDB^B%I{E<~D3=KNdU7^bbnrEFNV$%1f>Daf%Pw_R?-`zg5$h=Ao$;4%h!~)Vp zro#HHC$O8SK62xIava<)e<9fUY@;|vGE#!m2Nz>-HiYZgY-cRVSwE&*dV8Is`%9^T zz+8v5zBe84P+|gcL(WG-Tdx2?k{K ztG*vQu(i4L^{d(N z8egv8TW~cvM(M_p*JNk1w@axv{v2Uq2X(=Slvy=Brln!a%7M~aHpg`0jbia zO9FMl@iSSziK_mPw=#_ZW_6OE6Xz3W$2=y#1uplJ7OM__zgIF;%W)TfgS7d}+AGDn z8kSp3gd#B%>9j(i0oz+ZMCURN`aqbMw%aN}MK6(vMNB0eWt+~msN{OQ{Uy@g%gvR< z#2tqxn1{&7@1RK$6s@HA-7c9(ilBUdVSn<)rY7g&)q-Z;j%D}jErtW?dATJHVI{EACOAsIZP;m3 zDit{-|HQ~PXoE(f64BHI!U#@AA!tX$Sl2F~$qPZDOHN+H{Z#Z@aNIb!;snJCjlK~S zzlpNvX1f`uC3_2zxxMN?B09^LlTkK7-3n->1Y9`0f}J?7c0vh^*T+}9KUUAQta!RQ$)BvyPp1oRna=l@n*uwn_thJPL7B2 zP;)Nx35$FLkSofDVIPPwB=;2`HRssT%EJtV!g%6%1`Fr9w%=)$cy=#ydoV7l3g}F_;~OszdOfDw9)7Q? zY-XlS@qARoJgAfaC|GA?Rwlky-S+oYwY|rrY;noJuf;DmoMv&sL0t3038T&_W12;pXMFeFlDyale2`O+rBzP1$<~{CN#DpBk7VqZ zi1Mt0%eJvGhEJX6>Y`gug$nCX7^_*u4+=k{jnT=l~Yk2Kd`)CQ&fAjQ>J$J`P+Nq4T~)kd@t|nEdP*-c_<22tpdr%rxH zHAg9i1*z_=SzFS6aR|b`J@&l3GODlJPI=RUHtQqxpo{oO0A(RXL>q2e)@|(Kx5Vpo zk1>hA85xyf>`#)Ss^Tb`|T`RgeOW^WqbZI z;%}DqgbjF;6$0@z<#~cp;m_K;ESA^w=UJmyqkg0-^Jij-)7jrhfWhep2c&@Z|B~FG zxo#shXbM3XrV;B5=ndAxBL=TBI8tHYc?Sr$rv1+)n)$Jsi|GzRPL@scLy-<7XSC2U zCL(k2Mng+{yfg%^*uB-GS+9kw>KAJ%-~8bnG^d=;=4vCHmRY!1Qs(jNVK?#*5UBXN zNk;`~ueKQAY8Ay9`z<34xDclHd7t0ZhQgIwvAo~TWRv6`Xf)Tpa0ODv!h>7qvdzu# z&LoT5{2oi**@Fe=Xz6}|)ze{sbtm8T&iM32aM}+vr_i_8Yz|4`#$7($tHv~(AIg#R zzcHF#@AT6T?}TV4ZQot!h6v7|_;R+fb{uE-TLpfT=s%Fj?}!+N#qx75n+y6kt|^ZX8ziiJ!@OAp24yV~ z01VwRpD3&_8IgJK88#sUx8D{!_DK~6K;NFKvLE)~hM$iTPVv2kNIAHB^1R-$d=M(hux9w9r5~-Ek1eDYQj^M-+6wIqAKA>ok zbO*d8OL@p>DDZ9GT4)oej9PCx^^OMZ<2|B)Mly|S#>unZD&-tJk5COQY{dyCb+jjp z=p`(5jt5Q=KS3UBlW+8QP&>{G*`xZ+{9>UxjIuGhLZYUnFY|1*0}r#4;ec|gy zu3S1v2%9#)T)N}z#w%Y=W|1{|@$kk&aTTT{c?k0-leT*jj$2Ny^hkf+YxBR5!G4Ng zG0gab@+6_uNb}A_;RGG{jG`JpBJ>RFdwaS))!@)8S=!NSNEAI^mr}KLK`U zwx ze!n?S{3PduL_nJEDr$Oah7>A?S^c{B-F+<4#gm15j7}qq_r(5O6&_O1NCuT9WT~^3 zQDS|bgz;F|K50RJ4B7hIpCVSC#Gs+CvX(r)!pkOg9Z6!e#9YH*c>=X(Oo$+ ztusfNHWg<)kuz-NJ!U?wYCTzw<>)X_^_vcuCXS{vZ6dm5HLr4s1ue+keqUM*UnzK^f_u`oV+Y6RpEmK+k) zg-SCc<`+>XeoM$9B%(vLzT1(%e#snO3DY&fU`3~x0_Sa|WZ7@js_zr}?p#YH5g4bpF1#{GbdHhGM8UdL`O1L+FB zXnA3_jHvv|cZ zB-k`_uA450c|W!@Bpf$ad85<+$WV{_ST&xJx&Y~s9}gCUGvNZia6hA3p#IpPiMH2D zg~0X>an08_QQSc+LG^%I`>Ru%5vfdZLLD0x#?*C4G^r$KxIH|MZY$(t2f2Q-v10eT zp4vNWa+}$EcCnVv#T}XP0WsL$g>}k0+FAS4OX)CIqU{3^p?2vQ65`jfXZ4&;gJF{- z`|Bf_L(GUlSyr9dG`_gxZ$+7zAGAICe;b+Yps1k$#7CUrqglc8Xm{*JZHURD{GkRS zekjHVdlVUqv$I#3>cf)2(ub8w1Kz5d7O$$lF`+F0y7w_a0C`F5@i>R&xR9`eg#nPK z&Ljgfc&}d!7VH0xE~mf&&zCJQBHSJT3_Hp zI>=+TAQ%%r;zyFDwJ!B}nP7U*tEZdN!g=jx(~xp|&Maw*HQZR+bd} z(Dc4M?C!F>>!&kvjT)BCFL+F%adbq2o%bbm@eRk?dWY$TG%+lMY{zy;&JTrlIv%#C z*H{uUhIBu4TSs`yc(m_pd_d70pPWP9d~KGzET!FnydsH;^wgj9;2Zr)22VyMVxYF~ z#jOU)a$%@&WxbK9lzxxXJdDEPWCkY-hJWGdu@rYsK%F1d=Yx>jS)VS0QS?e}rlVex z2?gmXgUl0?q*KpProY!A-Z#>_-cce5n=8^ZZUyUvRULD=idm9ZQQhx6QnN zQMj;eEtn9uBartC2ZC>#QO%iSXchmX_^U?Kg3!&Au9f4S;C~2kcI8lZK=y50J*gt= zm;w0iahSy~+tAoJKlSdnA;x)KV%;-Q4%U5bYm+?Kn`Uyqz#c{48I~l>VKE@X)7uxu z7{6qM9aI84*{DL@s6|nz%{@D*Mzv_=BVKJbl<;(Jd! zZ$nJ`tG}IrYuYkczZq$)9jzY3uuVl8&_H*}ZP-_DvXdll-lez1(7Y6OD;d&;*Pvo~_LFd>XAq%BIj;>rN@XHD@9J1;T$Vs6>Lg{5(bxC`vfV0##=EY;WQ1rdIXQ=s=_6 zi0ILL5W!3LSuyJSCAM$@b&RtR4@=xD=))gqnk@zkNRIaBUb#)DS)2_2e5H=JOc5e} zbv@v|SckR2!Oi8%g)WHm_kf~^#LWjxEkWj~tJ_G^i9I`Ht(gh#7PiCXrJS|w#m3cp zq~Wy1x=-^wIpK@P0c0mBmCGckx0$GRoTviDBI`^~$kG;Fwm5++8M^(_0+vU?JoizS z({#`9xk}`v~;oL9A9%vX<9QZ&sK`et~7Akj(c$m=U$Z= zW8|zT6C9K9!Qw2*r_^xmcko)0bYpPfiO}Y_%eG=v}G2}~y^glf7{gndWI~Y># zfQPBEtrz?h-`u+wVeR;w!$NTTfRC+dboPFFG5CxcVC;Oge5`|7oUi*e?6e} z(FRvN@#5oPc&PA|t%3$k?j^Q)1Uu3!!0>s3&LS+!EMcW{r&9sG;xr>@b2F#({s8e8l@~Qr1cSiMB2$G`l2~G}vpQ zF>`jT(ZRvv5fg3R4kX~-{9=ciD;za?@I}bY{UuEL+OY2HPp$Lv?5oV&5wY}gZoU)T zJO!>A(OzRunr~w%`E~=>C!SYjVpHkP-y}p&&RCIn{!x1<*Z;&q!dgL0Hes?}Zn--j zbfp?xaY@s*f~_j&9ZVe#JA%z;EmG@Xk|vwf5_&k>)GE+%=f>ZSphr-A%8I2~Y6dkS z=a`eG)5Vnxq@5WgD{bFl|1;_!j@Z-0BLso+F%Sq*%}1;fq;rdvbRK+94d0ydv`u6) z-5*eq3Wg#*>Kp{%5AKg49pAI*dJHaTHOT>9vOK^!mgP6sHhEF7CTJ{c@mh03+5<}l zBCve0-q>mheQwkiI#`j_nP4Q2cw-b0TIul)<(HFf-4h9GLaZ$kwafPnxND&y{YjY& zmdiQ!cBe@-$!7ee*f(GtMLZvlUMl{q7{D9w7U30JU;TkpUcs=H4f3YPJT?4nJX5FUSSU_){6 zSjo*SHV1XfY}=}&aecbr+heJ~W$XZt1Ut(4yX^rI2wx$Bp(2W{BE)t8NnL$@bK>Pc zk$@+k)!RQW_m;`k*?{~G4DGX{DFtHd9Ck@(N2;QuhaI$EWS&>OU7yYip=Wz{LgSOi z%n_e|X>$4AQpU_0{S1Gk2yW=~Fvz*;X^=*JxZm{+LF21zMGf^yH`|4>#AFq9W=4J< z4|g#)sBNnoW1Y zyLl8SbGcjVSTO&_R)lHRU`mBbay~#rUk+-AxF80*L!dzu6Oh%$K%=KIW((ZTRxn%d zP7b6PK0T5wKRigjs?5Dq{Eoi|`a?d2bH}h(roM6)NVNmQzw}EZw z%S~^xTNG;0QPU}Fg^*H8(4j?*`dqZICKI&UD8OCU>EoGVkx-tPMhyS@m?rE1d^dk+ zyuVT>#D9ozKpcE@8$wy4#sn$e0mP1h$IlPxU!LAHWS!ON3@;ZwbNlh2aORBC#f0ss2CM#iUWH<~ z8)v4Dfof+sxv6aO+#tlx7M~l1hH*f)bG1lycwpZ^@>rhqW0zifzJ)LKcQA!OXb}gL zno}2I>dZJ;BRtX>>0N~_+LSptLZL-be}u6j#}$MsN0)|TEu_Vjq>?i5(C#TjxsaE8 z;}{VQ6;FePdn_B2p^>FWwr@@z5X*Iudne4VR=%JqlB*V|xt(1wo^3H&D3|LJcE|~Y z<@p7*#gp7&Kc)%yrb5;z>(@S_2K^CIU8wL^UML`EM4TQqeCI+QA_jJ`BuuryJbNo& zqtQc&a=CRqItatP3XO%e^Gw849qfsC+B_2Z1;fGygNK-%|412tT)(B?O(j^?MtCxG z%zf5MvDJ$~R8q&->uM9kXO>;GMS?9Wo6s20V`%RpD*f=3Fbl;61Nisb5$CmJOt2|ARF1y|LW=>KBVSkpuumUn?@w37;t~d?c64Sa|=Rc=l?_4zb-ObI@fhj-v~fHyI6|DZ?if|{q;?Y4tU zeS5uH=J5c+5rBcT-)=In{(oA4VjZ@1|6m*5mY{)&98l!t(XN^#mK_7W+1HUgcTv=F z&|D>4oiR=;JVIHMIV88Z>bl=Cxx>=4XY|arC8yY=6L6JFUm|lX+5f~5RuA>rJgUnG zCC5Pcr?MwYbqrFL$d?v!uBKHuBLf~A=@^CsTp2` zrd{ZN$_ZiP&(X)Fo&GD!q)O(UC42HIAwgu>Nz(AShuS%JhMWU9Gk`@~7oKqpq8UC# zOIMzB$Fo^a63uLn)F#$+lWW;Z^T*>eq_B@jyibNhF!9@J-<85Ql9FyRo-TM*{5wfH zUEDq^`|_7B)~p#D6{0kNq>mq$7o=^^eEk0#Vnh(&g!iZcG-2a3Ufrepy-m3qKmI_; zX|MexnQ9mi&J4Z#dlET%CG6iGz9i?_*_t6}&jSBJ=;gtxcH;9)U`+3coMbe@5E z_n){j2&3M8bxFW?jpyb}uPYC(Z0315{hL^X$u$&i`1(-1-#jp)nt`~iL}H8(SgN@- zY8YGYX;rSu^$RjRqZk45gwD-Z=o6>qQ6A|)t!_uB2`2|+*( zaB5FTYm9FLb@z1y`!^!!G>qI&pSVHIImtX8kJeob=%rglvfo~~_Hk4FJ1bTfe{lK_ z^G_O>UW@{2_mnh1;T|C>jOHNdtDU#JO~D!%HOs7yn!5c9TIqT3FIvEPMEZ8=@6cHp zlj=Prm|GTZ_9JFSX^fhFJv^AgK#%&XQ1F~?MBpxEiv408Y&Zg#>Oz{trp9^)W#F6} z418P0YGX)FE>GyqDgKxE&ou-k2!!8=_(tQvDPlL!9ae65|*VVrz z3U)+TpWL{7`&Sl~ITcLh{6dl0?0Hf{f~vE#LiWwC#PD;yLw(W(eI+*D@>=YFBH*zSz@a;yY z^Gp4+-5_K?YW;Lp@CDqr39&%CrvsvtrA7rz%&BLp<^I~w2M_!G-?99W%Qii*M(^lE zO+oeUk=h$a1O#z!5MiIps%t?(!`m%{>@7GrHm0{v{x!zB=zpT<%r+yPwV*N%8%#Gy z2t0|Blv4#_6zsR@b&Tsx3CgjTU0b-eU$QG(9GIFryH{3GNXH(;_KjG^<0DSqS%In$ z(VfZ?I|~dT@OCTtsF%i`H)P=PJ2v1O9#UE|jVU3_rdN&79zC)F)pu51`w@=XYvIb} z%1WKQ{+bfY@qJT6wD92r!6-%dqR*z~diI4O{n73IRp3gmU@Y}(cevM%Tc@7F(#Wjb zKGkedGW+kG80XdbwG3(|UeE8q%NjZc;)scVsvZ6Gy(r0XZJnS&eU#Q}>0vQ%i*T(r z<^-O2;W&g2TXiy*w%zn`1A+BtG4+AHVy$cium5u1TS3xtYNJo7vw2^Ev#N&j86_Y& zhpcCt&rzCW?W?(P{*ACWzkm>~Sl`~x;el{lEVhBRje?86q` z&i=TQZZu$f3DsAA%U$@)LTcDVyDf(0x&d?v)m4}rTnZ-=Nm5TeqB8>S@2pfNTgOK4 zIR!p~rE*5b4fUX%m%ooW`zHlD?OTIvv~B~7ld;~I{W8J@tioRfSI3L#(EgEGI!&V) zky-mG=id*tI>RjEibAihIKzb^!NxOaaPBua=})$~iYvBP$D^iBWqKRa`@!l?gA!e#}q3G|Yq1RS>S zkYLQB@qbS7!wlJ~xp2Yf-Zq`4=T}eF5kear^S8?TfaSd*A)n>Dd$nm|EG=pG$p*0VTvz`o^2`664PPda%LZ ztudXR*K$aT2oyg~7D2yCg5TM$-ug(sZ>6^Uuv#O)@D|lWQU)n(U

    _a~HX(rWOj}#q%h*Ae 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%0D_Ckz&~@al6n4g=%m?rc0bT+`V%Jg$JSxK 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%0D=WmHsc_j_EeBiiMYl_p`i70VEq%4= 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%0ANB^iC$V42e-j^y;+2c_Jj&0?qX=X&{7qH zWpH4PA~UkIU(AV_L*F&L!Ip&SAtPn?9Wq6Ka17)8Aai_kT<02b55&S<)Xjbs4Xhv` z2tXifRZC;B=QvI-4s|GBSQrbX7)F`(<0kc`_`e=k@Zlc?LI(`BwHPOnU3DF&L_b@V ze>YXtT@Ad3OCJ@rEpg%+*-R;hHK%XlDfRSn#cP#w*OZ;JwfPmoo_si_r(QE7~ zO<1{A|E*6PRUqW76B0U-X(HE z0){Dzrlz%qTA5B^pZoN}Z8hf@?R=4_K)omC3CKmMb+`33CT1IRp)%MwxI5iH;*f0a zSPZ?BV|cp~bMT-)@3~DQ@1jTdGp&w7(q{yu*fBt2gma4TCUBU z{24V%+qYj<%4Y>kMRCIK+03&oRyuN#fGbdXP@po)*g}%e7_Ig7vcF?~{oh3r`zfg{ zf~BJ(QucH4Z;EcjUxzJ)erkN#@K)|av}l`%@Vu6$BoC9Av@=ht%e{(6AnIP_&R^P< zlAu8y+%{O-b4e0%U0DNjv{Nor?~f?3mQ;0K2dwN|-b~;0Wg#;6&cpQuIf_$B1SfV4 zKJx0MlU_{g)6m}&{3ka6;mS`>o*mo1VOJbj1+QRb<=(_z!z!2tiaA`3TQdVY%(`SP zdI=t%T)6I?u;M5hf@LgtmjANi>Y?m^$~8KTKo|)x@CFKHVJh-IWbC84^vMYZ6P~w@ z^ZbxdZV@1`{;EL4i;U}dXXw*+t9~f$ne#Oahw)34TVZ=Rl@@zIg!|B+)(sd5u_7Aw zh=gHxzqs#;0?@ZB`xr?*uK@zf8B3AwaieNwwlI0~fWwytq@0x80Mx5*uEv1o`PscA z#&6Z5DJ2@WT~rVzfZrW8Ao)uZdHv;*M`a=#<*sB`nD(zL6#_wZ>@^W{0d@ndtPg<9 z&}>n1%FD?if=NDFT>-S&8dD|P2$X@Z8O)FEkmag=WmXU%d-nhg3KJs_XTn1^p?FWn zN5uGLfV(dL7)aIdNh0vuILD!fHjTWg#>9)R$&& zESuS|#?2&M@;ZmX@BA>bN;~Cfi&7g@K8-W5g%PWlVGL}(4LbMLm(a1f8XO$w_03$N zhJ95Vo#{?+KQH7=diOvh5kz>yxKAVEOEx9$szS%qc7aH;txY zKE>&ln^ylSZ~Gr1F~tAxIgA|=n$XF)eoU@hc{{kNjZ4)^Y1-h(LtjpS53kHC#6vw$ zmE2OAvI>b6W;RbBbve7Emg#C{vJ@M{?Q%iSI>qmx`rLVB4@hD|fP5EQxrIAgDB~P; z*{jFmz#&bOslAvgBCra;8WBU`m2K5Vv`JmQj8$xKm#8LA|3jr3#vIFfMNIVYzP@R9 z5MxI8%SA|J9$j>uPQP`xU#2-nn0x8R%vvtVk7WG1+9;*$h->e-KCpcc^G#Kbfqhh1 z@1N~yYj(iFyGQ6SdMf*-5cJ|h2uMoIQ6wDAtIZ3b! z#0A=Zc4l6Xy5wDY%00FqfUnltgwk}T@@-%9-#wq(u8xq5&ag%#(O-gt(Gza#=$wIa zLC8Nsupye#Rf7Ky8ioF(4$?b_7#x60ly=#pE|2PcAISsNBIP*HGf!wE$-U!h)p!@L&qh2+THqy4jr(g|c|DW~>4RZIDP-;FDz4&0- z?fgh-xi^IRYuQ9Zzj*85c{tU+GXl(GcY>CIArj@`bkluCyMF0i(7PPJ*hwzh&8}xk zn(Ch)p6wCm+Zr zT4}WSxi$7>l|N|*m}1=Ur{4BG;(8%YQ+u<>QFio%?#G@Vze2pa5T%Ly;{_rcs)C0JDPPfe0XK2dxreD^eOxvkLxRnklcQ~MPyk@w{ z+D2r=gtv}5PjTk+@Tq)62457Yz?yF3?Gi#V^(czOwdk@2s zi=v2)fj41uyEyOKzlM#{CqRL5G-!AY>mg2~J$Cq<3oVY6;<%p4k}G)p{Xv6uCuH^? z%vk;Q_b%wfiwVDLGD83X2s1HMt`is{6xAC*aLKzZD?4=#>5c`t%0D!_xj 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

    dp_(lt13 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≈6MV4tkGFi|L_+M&*?A$Nim93%A$x@zM3+Ldu2q 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 literal 0 HcmV?d00001 diff --git a/src/client/render/gl/assets/fx-atlas-meta.json b/src/client/render/gl/assets/fx-atlas-meta.json new file mode 100644 index 0000000000..b86c81e7c2 --- /dev/null +++ b/src/client/render/gl/assets/fx-atlas-meta.json @@ -0,0 +1,78 @@ +{ + "width": 540, + "height": 242, + "rows": [ + { + "yOffset": 0, + "height": 60, + "worldWidth": 60, + "worldHeight": 60 + }, + { + "yOffset": 60, + "height": 38, + "worldWidth": 48, + "worldHeight": 38 + }, + { + "yOffset": 98, + "height": 15, + "worldWidth": 17, + "worldHeight": 15 + }, + { + "yOffset": 113, + "height": 19, + "worldWidth": 19, + "worldHeight": 19 + }, + { + "yOffset": 132, + "height": 12, + "worldWidth": 13, + "worldHeight": 12 + }, + { + "yOffset": 144, + "height": 14, + "worldWidth": 16, + "worldHeight": 14 + }, + { + "yOffset": 158, + "height": 13, + "worldWidth": 7, + "worldHeight": 13 + }, + { + "yOffset": 171, + "height": 12, + "worldWidth": 11, + "worldHeight": 12 + }, + { + "yOffset": 183, + "height": 16, + "worldWidth": 24, + "worldHeight": 16 + }, + { + "yOffset": 199, + "height": 16, + "worldWidth": 24, + "worldHeight": 16 + }, + { + "yOffset": 215, + "height": 7, + "worldWidth": 9, + "worldHeight": 7 + }, + { + "yOffset": 222, + "height": 20, + "worldWidth": 21, + "worldHeight": 20 + } + ] +} diff --git a/src/client/render/gl/assets/fx-atlas.png b/src/client/render/gl/assets/fx-atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..475422437ac524957a7ff0b17a9554a0ce333174 GIT binary patch literal 7860 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", + "width": 39, + "height": 39, + "xoffset": -4, + "yoffset": 0, + "xadvance": 30, + "chnl": 15, + "x": 399, + "y": 550, + "page": 0 + }, + { + "id": 126, + "index": 97, + "char": "~", + "width": 39, + "height": 24, + "xoffset": -4, + "yoffset": 7, + "xadvance": 30, + "chnl": 15, + "x": 359, + "y": 582, + "page": 0 + }, + { + "id": 172, + "index": 110, + "char": "¬", + "width": 39, + "height": 31, + "xoffset": -4, + "yoffset": 5, + "xadvance": 30, + "chnl": 15, + "x": 319, + "y": 590, + "page": 0 + }, + { + "id": 215, + "index": 153, + "char": "×", + "width": 39, + "height": 39, + "xoffset": -4, + "yoffset": 0, + "xadvance": 30, + "chnl": 15, + "x": 439, + "y": 560, + "page": 0 + }, + { + "id": 42, + "index": 13, + "char": "*", + "width": 37, + "height": 37, + "xoffset": -6, + "yoffset": -6, + "xadvance": 26, + "chnl": 15, + "x": 0, + "y": 618, + "page": 0 + }, + { + "id": 170, + "index": 108, + "char": "ª", + "width": 33, + "height": 37, + "xoffset": -6, + "yoffset": -6, + "xadvance": 21, + "chnl": 15, + "x": 636, + "y": 509, + "page": 0 + }, + { + "id": 178, + "index": 116, + "char": "²", + "width": 30, + "height": 37, + "xoffset": -7, + "yoffset": -14, + "xadvance": 17, + "chnl": 15, + "x": 0, + "y": 656, + "page": 0 + }, + { + "id": 179, + "index": 117, + "char": "³", + "width": 31, + "height": 37, + "xoffset": -7, + "yoffset": -14, + "xadvance": 17, + "chnl": 15, + "x": 635, + "y": 547, + "page": 0 + }, + { + "id": 186, + "index": 124, + "char": "º", + "width": 34, + "height": 37, + "xoffset": -6, + "yoffset": -6, + "xadvance": 22, + "chnl": 15, + "x": 399, + "y": 590, + "page": 0 + }, + { + "id": 185, + "index": 123, + "char": "¹", + "width": 23, + "height": 36, + "xoffset": -8, + "yoffset": -14, + "xadvance": 10, + "chnl": 15, + "x": 480, + "y": 525, + "page": 0 + }, + { + "id": 34, + "index": 5, + "char": "\"", + "width": 33, + "height": 30, + "xoffset": -6, + "yoffset": -6, + "xadvance": 21, + "chnl": 15, + "x": 359, + "y": 607, + "page": 0 + }, + { + "id": 176, + "index": 114, + "char": "°", + "width": 33, + "height": 33, + "xoffset": -7, + "yoffset": -11, + "xadvance": 19, + "chnl": 15, + "x": 586, + "y": 572, + "page": 0 + }, + { + "id": 175, + "index": 113, + "char": "¯", + "width": 32, + "height": 21, + "xoffset": -5, + "yoffset": -5, + "xadvance": 21, + "chnl": 15, + "x": 479, + "y": 564, + "page": 0 + }, + { + "id": 168, + "index": 106, + "char": "¨", + "width": 31, + "height": 23, + "xoffset": -5, + "yoffset": -7, + "xadvance": 21, + "chnl": 15, + "x": 512, + "y": 564, + "page": 0 + }, + { + "id": 39, + "index": 10, + "char": "'", + "width": 23, + "height": 30, + "xoffset": -6, + "yoffset": -6, + "xadvance": 11, + "chnl": 15, + "x": 996, + "y": 196, + "page": 0 + }, + { + "id": 44, + "index": 15, + "char": ",", + "width": 24, + "height": 30, + "xoffset": -6, + "yoffset": 21, + "xadvance": 11, + "chnl": 15, + "x": 251, + "y": 550, + "page": 0 + }, + { + "id": 96, + "index": 67, + "char": "`", + "width": 30, + "height": 23, + "xoffset": -5, + "yoffset": -7, + "xadvance": 21, + "chnl": 15, + "x": 479, + "y": 586, + "page": 0 + }, + { + "id": 180, + "index": 118, + "char": "´", + "width": 30, + "height": 23, + "xoffset": -4, + "yoffset": -7, + "xadvance": 21, + "chnl": 15, + "x": 510, + "y": 588, + "page": 0 + }, + { + "id": 45, + "index": 16, + "char": "-", + "width": 28, + "height": 21, + "xoffset": -4, + "yoffset": 11, + "xadvance": 19, + "chnl": 15, + "x": 319, + "y": 506, + "page": 0 + }, + { + "id": 173, + "index": 111, + "char": "­", + "width": 28, + "height": 21, + "xoffset": -4, + "yoffset": 11, + "xadvance": 19, + "chnl": 15, + "x": 319, + "y": 528, + "page": 0 + }, + { + "id": 184, + "index": 122, + "char": "¸", + "width": 27, + "height": 28, + "xoffset": -3, + "yoffset": 28, + "xadvance": 21, + "chnl": 15, + "x": 251, + "y": 581, + "page": 0 + }, + { + "id": 46, + "index": 17, + "char": ".", + "width": 24, + "height": 24, + "xoffset": -6, + "yoffset": 21, + "xadvance": 11, + "chnl": 15, + "x": 219, + "y": 592, + "page": 0 + }, + { + "id": 183, + "index": 121, + "char": "·", + "width": 24, + "height": 24, + "xoffset": -6, + "yoffset": 7, + "xadvance": 11, + "chnl": 15, + "x": 279, + "y": 592, + "page": 0 + }, + { + "id": 32, + "index": 3, + "char": " ", + "width": 0, + "height": 0, + "xoffset": -8, + "yoffset": 28, + "xadvance": 11, + "chnl": 15, + "x": 66, + "y": 62, + "page": 0 + }, + { + "id": 160, + "index": 98, + "char": " ", + "width": 0, + "height": 0, + "xoffset": -8, + "yoffset": 28, + "xadvance": 11, + "chnl": 15, + "x": 182, + "y": 61, + "page": 0 + } + ], + "info": { + "face": "overpass-bold", + "size": 48, + "bold": 0, + "italic": 0, + "charset": [ + " ", + "!", + "\"", + "#", + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ":", + ";", + "<", + "=", + ">", + "?", + "@", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "[", + "\\", + "]", + "^", + "_", + "`", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "{", + "|", + "}", + "~", + " ", + "¡", + "¢", + "£", + "¤", + "¥", + "¦", + "§", + "¨", + "©", + "ª", + "«", + "¬", + "­", + "®", + "¯", + "°", + "±", + "²", + "³", + "´", + "µ", + "¶", + "·", + "¸", + "¹", + "º", + "»", + "¼", + "½", + "¾", + "¿", + "À", + "Á", + "Â", + "Ã", + "Ä", + "Å", + "Æ", + "Ç", + "È", + "É", + "Ê", + "Ë", + "Ì", + "Í", + "Î", + "Ï", + "Ð", + "Ñ", + "Ò", + "Ó", + "Ô", + "Õ", + "Ö", + "×", + "Ø", + "Ù", + "Ú", + "Û", + "Ü", + "Ý", + "Þ", + "ß", + "à", + "á", + "â", + "ã", + "ä", + "å", + "æ", + "ç", + "è", + "é", + "ê", + "ë", + "ì", + "í", + "î", + "ï", + "ð", + "ñ", + "ò", + "ó", + "ô", + "õ", + "ö", + "÷", + "ø", + "ù", + "ú", + "û", + "ü", + "ý", + "þ", + "ÿ", + "Ā", + "ā", + "Ă", + "ă", + "Ą", + "ą", + "Ć", + "ć", + "Ĉ", + "ĉ", + "Ċ", + "ċ", + "Č", + "č", + "Ď", + "ď", + "Đ", + "đ", + "Ē", + "ē", + "Ĕ", + "ĕ", + "Ė", + "ė", + "Ę", + "ę", + "Ě", + "ě", + "Ĝ", + "ĝ", + "Ğ", + "ğ", + "Ġ", + "ġ", + "Ģ", + "ģ", + "Ĥ", + "ĥ", + "Ħ", + "ħ", + "Ĩ", + "ĩ", + "Ī", + "ī", + "Ĭ", + "ĭ", + "Į", + "į", + "İ", + "ı", + "IJ", + "ij", + "Ĵ", + "ĵ", + "Ķ", + "ķ", + "ĸ", + "Ĺ", + "ĺ", + "Ļ", + "ļ", + "Ľ", + "ľ", + "Ŀ", + "ŀ", + "Ł", + "ł", + "Ń", + "ń", + "Ņ", + "ņ", + "Ň", + "ň", + "ʼn", + "Ŋ", + "ŋ", + "Ō", + "ō", + "Ŏ", + "ŏ", + "Ő", + "ő", + "Œ", + "œ", + "Ŕ", + "ŕ", + "Ŗ", + "ŗ", + "Ř", + "ř", + "Ś", + "ś", + "Ŝ", + "ŝ", + "Ş", + "ş", + "Š", + "š", + "Ţ", + "ţ", + "Ť", + "ť", + "Ŧ", + "ŧ", + "Ũ", + "ũ", + "Ū", + "ū", + "Ŭ", + "ŭ", + "Ů", + "ů", + "Ű", + "ű", + "Ų", + "ų", + "Ŵ", + "ŵ", + "Ŷ", + "ŷ", + "Ÿ", + "Ź", + "ź", + "Ż", + "ż", + "Ž", + "ž", + "ſ" + ], + "unicode": 1, + "stretchH": 100, + "smooth": 1, + "aa": 1, + "padding": [8, 8, 8, 8], + "spacing": [0, 0], + "outline": 0 + }, + "common": { + "lineHeight": 48, + "base": 36, + "scaleW": 1024, + "scaleH": 1024, + "pages": 1, + "packed": 0, + "alphaChnl": 0, + "redChnl": 0, + "greenChnl": 0, + "blueChnl": 0 + }, + "distanceField": { + "fieldType": "msdf", + "distanceRange": 16 + }, + "kernings": [ + { + "first": 32, + "second": 65, + "amount": -2 + }, + { + "first": 32, + "second": 84, + "amount": -1 + }, + { + "first": 32, + "second": 86, + "amount": -2 + }, + { + "first": 32, + "second": 87, + "amount": 0 + }, + { + "first": 32, + "second": 89, + "amount": -2 + }, + { + "first": 32, + "second": 192, + "amount": -2 + }, + { + "first": 32, + "second": 193, + "amount": -2 + }, + { + "first": 32, + "second": 194, + "amount": -2 + }, + { + "first": 32, + "second": 195, + "amount": -2 + }, + { + "first": 32, + "second": 196, + "amount": -2 + }, + { + "first": 32, + "second": 197, + "amount": -2 + }, + { + "first": 32, + "second": 221, + "amount": -2 + }, + { + "first": 32, + "second": 376, + "amount": -2 + }, + { + "first": 38, + "second": 74, + "amount": 0 + }, + { + "first": 38, + "second": 83, + "amount": 0 + }, + { + "first": 38, + "second": 84, + "amount": -5 + }, + { + "first": 38, + "second": 86, + "amount": -6 + }, + { + "first": 38, + "second": 88, + "amount": 0 + }, + { + "first": 38, + "second": 89, + "amount": -6 + }, + { + "first": 38, + "second": 117, + "amount": -1 + }, + { + "first": 38, + "second": 119, + "amount": -1 + }, + { + "first": 38, + "second": 221, + "amount": -6 + }, + { + "first": 38, + "second": 250, + "amount": -1 + }, + { + "first": 38, + "second": 376, + "amount": -6 + }, + { + "first": 40, + "second": 65, + "amount": -1 + }, + { + "first": 40, + "second": 74, + "amount": -1 + }, + { + "first": 40, + "second": 84, + "amount": 0 + }, + { + "first": 40, + "second": 86, + "amount": -1 + }, + { + "first": 40, + "second": 87, + "amount": 0 + }, + { + "first": 40, + "second": 89, + "amount": -1 + }, + { + "first": 40, + "second": 106, + "amount": 4 + }, + { + "first": 40, + "second": 192, + "amount": -1 + }, + { + "first": 40, + "second": 193, + "amount": -1 + }, + { + "first": 40, + "second": 194, + "amount": -1 + }, + { + "first": 40, + "second": 195, + "amount": -1 + }, + { + "first": 40, + "second": 196, + "amount": -1 + }, + { + "first": 40, + "second": 197, + "amount": -1 + }, + { + "first": 40, + "second": 221, + "amount": -1 + }, + { + "first": 40, + "second": 376, + "amount": -1 + }, + { + "first": 42, + "second": 65, + "amount": -4 + }, + { + "first": 42, + "second": 74, + "amount": -7 + }, + { + "first": 42, + "second": 99, + "amount": -2 + }, + { + "first": 42, + "second": 100, + "amount": -2 + }, + { + "first": 42, + "second": 101, + "amount": -2 + }, + { + "first": 42, + "second": 103, + "amount": -2 + }, + { + "first": 42, + "second": 111, + "amount": -2 + }, + { + "first": 42, + "second": 113, + "amount": -2 + }, + { + "first": 42, + "second": 115, + "amount": -1 + }, + { + "first": 42, + "second": 192, + "amount": -4 + }, + { + "first": 42, + "second": 193, + "amount": -4 + }, + { + "first": 42, + "second": 194, + "amount": -4 + }, + { + "first": 42, + "second": 195, + "amount": -4 + }, + { + "first": 42, + "second": 196, + "amount": -4 + }, + { + "first": 42, + "second": 197, + "amount": -4 + }, + { + "first": 42, + "second": 231, + "amount": -2 + }, + { + "first": 42, + "second": 248, + "amount": -2 + }, + { + "first": 42, + "second": 339, + "amount": -2 + }, + { + "first": 44, + "second": 64, + "amount": -1 + }, + { + "first": 44, + "second": 67, + "amount": -1 + }, + { + "first": 44, + "second": 71, + "amount": -1 + }, + { + "first": 44, + "second": 79, + "amount": -1 + }, + { + "first": 44, + "second": 81, + "amount": -1 + }, + { + "first": 44, + "second": 83, + "amount": 0 + }, + { + "first": 44, + "second": 84, + "amount": -4 + }, + { + "first": 44, + "second": 86, + "amount": -5 + }, + { + "first": 44, + "second": 87, + "amount": -1 + }, + { + "first": 44, + "second": 89, + "amount": -5 + }, + { + "first": 44, + "second": 99, + "amount": -1 + }, + { + "first": 44, + "second": 100, + "amount": -1 + }, + { + "first": 44, + "second": 101, + "amount": -1 + }, + { + "first": 44, + "second": 102, + "amount": 0 + }, + { + "first": 44, + "second": 103, + "amount": -1 + }, + { + "first": 44, + "second": 106, + "amount": 0 + }, + { + "first": 44, + "second": 111, + "amount": -1 + }, + { + "first": 44, + "second": 113, + "amount": -1 + }, + { + "first": 44, + "second": 119, + "amount": -1 + }, + { + "first": 44, + "second": 169, + "amount": -1 + }, + { + "first": 44, + "second": 174, + "amount": -1 + }, + { + "first": 44, + "second": 199, + "amount": -1 + }, + { + "first": 44, + "second": 210, + "amount": -1 + }, + { + "first": 44, + "second": 211, + "amount": -1 + }, + { + "first": 44, + "second": 212, + "amount": -1 + }, + { + "first": 44, + "second": 213, + "amount": -1 + }, + { + "first": 44, + "second": 214, + "amount": -1 + }, + { + "first": 44, + "second": 216, + "amount": -1 + }, + { + "first": 44, + "second": 221, + "amount": -5 + }, + { + "first": 44, + "second": 231, + "amount": -1 + }, + { + "first": 44, + "second": 248, + "amount": -1 + }, + { + "first": 44, + "second": 338, + "amount": -1 + }, + { + "first": 44, + "second": 339, + "amount": -1 + }, + { + "first": 44, + "second": 376, + "amount": -5 + }, + { + "first": 45, + "second": 84, + "amount": -2 + }, + { + "first": 45, + "second": 86, + "amount": -1 + }, + { + "first": 45, + "second": 88, + "amount": -1 + }, + { + "first": 45, + "second": 89, + "amount": -1 + }, + { + "first": 45, + "second": 116, + "amount": 0 + }, + { + "first": 45, + "second": 221, + "amount": -1 + }, + { + "first": 45, + "second": 376, + "amount": -1 + }, + { + "first": 46, + "second": 64, + "amount": -1 + }, + { + "first": 46, + "second": 67, + "amount": -1 + }, + { + "first": 46, + "second": 71, + "amount": -1 + }, + { + "first": 46, + "second": 79, + "amount": -1 + }, + { + "first": 46, + "second": 81, + "amount": -1 + }, + { + "first": 46, + "second": 83, + "amount": 0 + }, + { + "first": 46, + "second": 84, + "amount": -4 + }, + { + "first": 46, + "second": 86, + "amount": -5 + }, + { + "first": 46, + "second": 87, + "amount": -1 + }, + { + "first": 46, + "second": 89, + "amount": -5 + }, + { + "first": 46, + "second": 99, + "amount": -1 + }, + { + "first": 46, + "second": 100, + "amount": -1 + }, + { + "first": 46, + "second": 101, + "amount": -1 + }, + { + "first": 46, + "second": 102, + "amount": 0 + }, + { + "first": 46, + "second": 103, + "amount": -1 + }, + { + "first": 46, + "second": 106, + "amount": 0 + }, + { + "first": 46, + "second": 111, + "amount": -1 + }, + { + "first": 46, + "second": 113, + "amount": -1 + }, + { + "first": 46, + "second": 119, + "amount": -1 + }, + { + "first": 46, + "second": 169, + "amount": -1 + }, + { + "first": 46, + "second": 174, + "amount": -1 + }, + { + "first": 46, + "second": 199, + "amount": -1 + }, + { + "first": 46, + "second": 210, + "amount": -1 + }, + { + "first": 46, + "second": 211, + "amount": -1 + }, + { + "first": 46, + "second": 212, + "amount": -1 + }, + { + "first": 46, + "second": 213, + "amount": -1 + }, + { + "first": 46, + "second": 214, + "amount": -1 + }, + { + "first": 46, + "second": 216, + "amount": -1 + }, + { + "first": 46, + "second": 221, + "amount": -5 + }, + { + "first": 46, + "second": 231, + "amount": -1 + }, + { + "first": 46, + "second": 248, + "amount": -1 + }, + { + "first": 46, + "second": 338, + "amount": -1 + }, + { + "first": 46, + "second": 339, + "amount": -1 + }, + { + "first": 46, + "second": 376, + "amount": -5 + }, + { + "first": 47, + "second": 64, + "amount": -1 + }, + { + "first": 47, + "second": 65, + "amount": -4 + }, + { + "first": 47, + "second": 67, + "amount": -1 + }, + { + "first": 47, + "second": 71, + "amount": -1 + }, + { + "first": 47, + "second": 74, + "amount": -6 + }, + { + "first": 47, + "second": 79, + "amount": -1 + }, + { + "first": 47, + "second": 81, + "amount": -1 + }, + { + "first": 47, + "second": 83, + "amount": 0 + }, + { + "first": 47, + "second": 84, + "amount": 0 + }, + { + "first": 47, + "second": 86, + "amount": 0 + }, + { + "first": 47, + "second": 87, + "amount": 0 + }, + { + "first": 47, + "second": 88, + "amount": -1 + }, + { + "first": 47, + "second": 89, + "amount": 0 + }, + { + "first": 47, + "second": 97, + "amount": -3 + }, + { + "first": 47, + "second": 98, + "amount": 0 + }, + { + "first": 47, + "second": 99, + "amount": -2 + }, + { + "first": 47, + "second": 100, + "amount": -2 + }, + { + "first": 47, + "second": 101, + "amount": -2 + }, + { + "first": 47, + "second": 103, + "amount": -2 + }, + { + "first": 47, + "second": 104, + "amount": 0 + }, + { + "first": 47, + "second": 105, + "amount": 0 + }, + { + "first": 47, + "second": 107, + "amount": 0 + }, + { + "first": 47, + "second": 108, + "amount": 0 + }, + { + "first": 47, + "second": 109, + "amount": -1 + }, + { + "first": 47, + "second": 110, + "amount": -1 + }, + { + "first": 47, + "second": 111, + "amount": -2 + }, + { + "first": 47, + "second": 112, + "amount": -1 + }, + { + "first": 47, + "second": 113, + "amount": -2 + }, + { + "first": 47, + "second": 114, + "amount": -1 + }, + { + "first": 47, + "second": 115, + "amount": -1 + }, + { + "first": 47, + "second": 117, + "amount": -1 + }, + { + "first": 47, + "second": 120, + "amount": -1 + }, + { + "first": 47, + "second": 169, + "amount": -1 + }, + { + "first": 47, + "second": 174, + "amount": -1 + }, + { + "first": 47, + "second": 181, + "amount": -1 + }, + { + "first": 47, + "second": 192, + "amount": -4 + }, + { + "first": 47, + "second": 193, + "amount": -4 + }, + { + "first": 47, + "second": 194, + "amount": -4 + }, + { + "first": 47, + "second": 195, + "amount": -4 + }, + { + "first": 47, + "second": 196, + "amount": -4 + }, + { + "first": 47, + "second": 197, + "amount": -4 + }, + { + "first": 47, + "second": 199, + "amount": -1 + }, + { + "first": 47, + "second": 210, + "amount": -1 + }, + { + "first": 47, + "second": 211, + "amount": -1 + }, + { + "first": 47, + "second": 212, + "amount": -1 + }, + { + "first": 47, + "second": 213, + "amount": -1 + }, + { + "first": 47, + "second": 214, + "amount": -1 + }, + { + "first": 47, + "second": 216, + "amount": -1 + }, + { + "first": 47, + "second": 221, + "amount": 0 + }, + { + "first": 47, + "second": 223, + "amount": 0 + }, + { + "first": 47, + "second": 230, + "amount": -3 + }, + { + "first": 47, + "second": 231, + "amount": -2 + }, + { + "first": 47, + "second": 232, + "amount": -1 + }, + { + "first": 47, + "second": 233, + "amount": -1 + }, + { + "first": 47, + "second": 234, + "amount": -1 + }, + { + "first": 47, + "second": 235, + "amount": -1 + }, + { + "first": 47, + "second": 240, + "amount": -1 + }, + { + "first": 47, + "second": 242, + "amount": -1 + }, + { + "first": 47, + "second": 243, + "amount": -1 + }, + { + "first": 47, + "second": 244, + "amount": -1 + }, + { + "first": 47, + "second": 245, + "amount": -1 + }, + { + "first": 47, + "second": 246, + "amount": -1 + }, + { + "first": 47, + "second": 248, + "amount": -2 + }, + { + "first": 47, + "second": 250, + "amount": -1 + }, + { + "first": 47, + "second": 254, + "amount": 0 + }, + { + "first": 47, + "second": 338, + "amount": -1 + }, + { + "first": 47, + "second": 339, + "amount": -2 + }, + { + "first": 47, + "second": 376, + "amount": 0 + }, + { + "first": 48, + "second": 65, + "amount": -1 + }, + { + "first": 48, + "second": 84, + "amount": -1 + }, + { + "first": 48, + "second": 86, + "amount": -2 + }, + { + "first": 48, + "second": 87, + "amount": 0 + }, + { + "first": 48, + "second": 88, + "amount": -1 + }, + { + "first": 48, + "second": 89, + "amount": -2 + }, + { + "first": 48, + "second": 115, + "amount": 0 + }, + { + "first": 48, + "second": 119, + "amount": 0 + }, + { + "first": 48, + "second": 192, + "amount": -1 + }, + { + "first": 48, + "second": 193, + "amount": -1 + }, + { + "first": 48, + "second": 194, + "amount": -1 + }, + { + "first": 48, + "second": 195, + "amount": -1 + }, + { + "first": 48, + "second": 196, + "amount": -1 + }, + { + "first": 48, + "second": 197, + "amount": -1 + }, + { + "first": 48, + "second": 221, + "amount": -2 + }, + { + "first": 48, + "second": 376, + "amount": -2 + }, + { + "first": 49, + "second": 65, + "amount": -1 + }, + { + "first": 49, + "second": 86, + "amount": -2 + }, + { + "first": 49, + "second": 87, + "amount": 0 + }, + { + "first": 49, + "second": 88, + "amount": -1 + }, + { + "first": 49, + "second": 89, + "amount": -2 + }, + { + "first": 49, + "second": 90, + "amount": -1 + }, + { + "first": 49, + "second": 97, + "amount": -1 + }, + { + "first": 49, + "second": 115, + "amount": 0 + }, + { + "first": 49, + "second": 192, + "amount": -1 + }, + { + "first": 49, + "second": 193, + "amount": -1 + }, + { + "first": 49, + "second": 194, + "amount": -1 + }, + { + "first": 49, + "second": 195, + "amount": -1 + }, + { + "first": 49, + "second": 196, + "amount": -1 + }, + { + "first": 49, + "second": 197, + "amount": -1 + }, + { + "first": 49, + "second": 221, + "amount": -2 + }, + { + "first": 49, + "second": 230, + "amount": -1 + }, + { + "first": 49, + "second": 376, + "amount": -2 + }, + { + "first": 50, + "second": 84, + "amount": 0 + }, + { + "first": 50, + "second": 86, + "amount": -1 + }, + { + "first": 50, + "second": 87, + "amount": 0 + }, + { + "first": 50, + "second": 88, + "amount": 0 + }, + { + "first": 50, + "second": 89, + "amount": -1 + }, + { + "first": 50, + "second": 221, + "amount": -1 + }, + { + "first": 50, + "second": 376, + "amount": -1 + }, + { + "first": 51, + "second": 65, + "amount": -1 + }, + { + "first": 51, + "second": 84, + "amount": -1 + }, + { + "first": 51, + "second": 86, + "amount": -1 + }, + { + "first": 51, + "second": 87, + "amount": 0 + }, + { + "first": 51, + "second": 88, + "amount": -1 + }, + { + "first": 51, + "second": 89, + "amount": -1 + }, + { + "first": 51, + "second": 90, + "amount": 0 + }, + { + "first": 51, + "second": 192, + "amount": -1 + }, + { + "first": 51, + "second": 193, + "amount": -1 + }, + { + "first": 51, + "second": 194, + "amount": -1 + }, + { + "first": 51, + "second": 195, + "amount": -1 + }, + { + "first": 51, + "second": 196, + "amount": -1 + }, + { + "first": 51, + "second": 197, + "amount": -1 + }, + { + "first": 51, + "second": 221, + "amount": -1 + }, + { + "first": 51, + "second": 376, + "amount": -1 + }, + { + "first": 52, + "second": 65, + "amount": 0 + }, + { + "first": 52, + "second": 84, + "amount": -1 + }, + { + "first": 52, + "second": 86, + "amount": -2 + }, + { + "first": 52, + "second": 87, + "amount": -1 + }, + { + "first": 52, + "second": 88, + "amount": 0 + }, + { + "first": 52, + "second": 89, + "amount": -2 + }, + { + "first": 52, + "second": 115, + "amount": 0 + }, + { + "first": 52, + "second": 119, + "amount": 0 + }, + { + "first": 52, + "second": 192, + "amount": 0 + }, + { + "first": 52, + "second": 193, + "amount": 0 + }, + { + "first": 52, + "second": 194, + "amount": 0 + }, + { + "first": 52, + "second": 195, + "amount": 0 + }, + { + "first": 52, + "second": 196, + "amount": 0 + }, + { + "first": 52, + "second": 197, + "amount": 0 + }, + { + "first": 52, + "second": 221, + "amount": -2 + }, + { + "first": 52, + "second": 376, + "amount": -2 + }, + { + "first": 53, + "second": 65, + "amount": -1 + }, + { + "first": 53, + "second": 84, + "amount": 0 + }, + { + "first": 53, + "second": 86, + "amount": -1 + }, + { + "first": 53, + "second": 88, + "amount": 0 + }, + { + "first": 53, + "second": 89, + "amount": -1 + }, + { + "first": 53, + "second": 90, + "amount": 0 + }, + { + "first": 53, + "second": 119, + "amount": 0 + }, + { + "first": 53, + "second": 192, + "amount": -1 + }, + { + "first": 53, + "second": 193, + "amount": -1 + }, + { + "first": 53, + "second": 194, + "amount": -1 + }, + { + "first": 53, + "second": 195, + "amount": -1 + }, + { + "first": 53, + "second": 196, + "amount": -1 + }, + { + "first": 53, + "second": 197, + "amount": -1 + }, + { + "first": 53, + "second": 221, + "amount": -1 + }, + { + "first": 53, + "second": 376, + "amount": -1 + }, + { + "first": 54, + "second": 65, + "amount": -1 + }, + { + "first": 54, + "second": 84, + "amount": -2 + }, + { + "first": 54, + "second": 86, + "amount": -2 + }, + { + "first": 54, + "second": 87, + "amount": 0 + }, + { + "first": 54, + "second": 88, + "amount": -1 + }, + { + "first": 54, + "second": 89, + "amount": -2 + }, + { + "first": 54, + "second": 90, + "amount": 0 + }, + { + "first": 54, + "second": 119, + "amount": 0 + }, + { + "first": 54, + "second": 192, + "amount": -1 + }, + { + "first": 54, + "second": 193, + "amount": -1 + }, + { + "first": 54, + "second": 194, + "amount": -1 + }, + { + "first": 54, + "second": 195, + "amount": -1 + }, + { + "first": 54, + "second": 196, + "amount": -1 + }, + { + "first": 54, + "second": 197, + "amount": -1 + }, + { + "first": 54, + "second": 221, + "amount": -2 + }, + { + "first": 54, + "second": 376, + "amount": -2 + }, + { + "first": 55, + "second": 65, + "amount": -3 + }, + { + "first": 55, + "second": 83, + "amount": 0 + }, + { + "first": 55, + "second": 87, + "amount": 0 + }, + { + "first": 55, + "second": 88, + "amount": 0 + }, + { + "first": 55, + "second": 97, + "amount": -1 + }, + { + "first": 55, + "second": 99, + "amount": -2 + }, + { + "first": 55, + "second": 100, + "amount": -2 + }, + { + "first": 55, + "second": 101, + "amount": -2 + }, + { + "first": 55, + "second": 103, + "amount": -2 + }, + { + "first": 55, + "second": 109, + "amount": 0 + }, + { + "first": 55, + "second": 110, + "amount": 0 + }, + { + "first": 55, + "second": 111, + "amount": -2 + }, + { + "first": 55, + "second": 112, + "amount": 0 + }, + { + "first": 55, + "second": 113, + "amount": -2 + }, + { + "first": 55, + "second": 114, + "amount": 0 + }, + { + "first": 55, + "second": 115, + "amount": -1 + }, + { + "first": 55, + "second": 117, + "amount": -1 + }, + { + "first": 55, + "second": 119, + "amount": 0 + }, + { + "first": 55, + "second": 181, + "amount": 0 + }, + { + "first": 55, + "second": 192, + "amount": -3 + }, + { + "first": 55, + "second": 193, + "amount": -3 + }, + { + "first": 55, + "second": 194, + "amount": -3 + }, + { + "first": 55, + "second": 195, + "amount": -3 + }, + { + "first": 55, + "second": 196, + "amount": -3 + }, + { + "first": 55, + "second": 197, + "amount": -3 + }, + { + "first": 55, + "second": 230, + "amount": -1 + }, + { + "first": 55, + "second": 231, + "amount": -2 + }, + { + "first": 55, + "second": 248, + "amount": -2 + }, + { + "first": 55, + "second": 250, + "amount": -1 + }, + { + "first": 55, + "second": 339, + "amount": -2 + }, + { + "first": 56, + "second": 65, + "amount": -1 + }, + { + "first": 56, + "second": 84, + "amount": -1 + }, + { + "first": 56, + "second": 86, + "amount": -3 + }, + { + "first": 56, + "second": 87, + "amount": 0 + }, + { + "first": 56, + "second": 88, + "amount": 0 + }, + { + "first": 56, + "second": 89, + "amount": -3 + }, + { + "first": 56, + "second": 90, + "amount": 0 + }, + { + "first": 56, + "second": 119, + "amount": 0 + }, + { + "first": 56, + "second": 192, + "amount": -1 + }, + { + "first": 56, + "second": 193, + "amount": -1 + }, + { + "first": 56, + "second": 194, + "amount": -1 + }, + { + "first": 56, + "second": 195, + "amount": -1 + }, + { + "first": 56, + "second": 196, + "amount": -1 + }, + { + "first": 56, + "second": 197, + "amount": -1 + }, + { + "first": 56, + "second": 221, + "amount": -3 + }, + { + "first": 56, + "second": 376, + "amount": -3 + }, + { + "first": 57, + "second": 65, + "amount": -2 + }, + { + "first": 57, + "second": 84, + "amount": 0 + }, + { + "first": 57, + "second": 86, + "amount": -2 + }, + { + "first": 57, + "second": 87, + "amount": 0 + }, + { + "first": 57, + "second": 88, + "amount": -1 + }, + { + "first": 57, + "second": 89, + "amount": -2 + }, + { + "first": 57, + "second": 90, + "amount": 0 + }, + { + "first": 57, + "second": 115, + "amount": 0 + }, + { + "first": 57, + "second": 119, + "amount": 0 + }, + { + "first": 57, + "second": 192, + "amount": -2 + }, + { + "first": 57, + "second": 193, + "amount": -2 + }, + { + "first": 57, + "second": 194, + "amount": -2 + }, + { + "first": 57, + "second": 195, + "amount": -2 + }, + { + "first": 57, + "second": 196, + "amount": -2 + }, + { + "first": 57, + "second": 197, + "amount": -2 + }, + { + "first": 57, + "second": 221, + "amount": -2 + }, + { + "first": 57, + "second": 376, + "amount": -2 + }, + { + "first": 58, + "second": 84, + "amount": -1 + }, + { + "first": 58, + "second": 86, + "amount": -4 + }, + { + "first": 58, + "second": 87, + "amount": 0 + }, + { + "first": 58, + "second": 89, + "amount": -4 + }, + { + "first": 58, + "second": 106, + "amount": 0 + }, + { + "first": 58, + "second": 221, + "amount": -4 + }, + { + "first": 58, + "second": 376, + "amount": -4 + }, + { + "first": 59, + "second": 84, + "amount": -1 + }, + { + "first": 59, + "second": 86, + "amount": -4 + }, + { + "first": 59, + "second": 87, + "amount": 0 + }, + { + "first": 59, + "second": 89, + "amount": -4 + }, + { + "first": 59, + "second": 106, + "amount": 0 + }, + { + "first": 59, + "second": 221, + "amount": -4 + }, + { + "first": 59, + "second": 376, + "amount": -4 + }, + { + "first": 64, + "second": 74, + "amount": -5 + }, + { + "first": 64, + "second": 86, + "amount": -2 + }, + { + "first": 64, + "second": 89, + "amount": -2 + }, + { + "first": 64, + "second": 221, + "amount": -2 + }, + { + "first": 64, + "second": 376, + "amount": -2 + }, + { + "first": 65, + "second": 32, + "amount": -2 + }, + { + "first": 65, + "second": 38, + "amount": -1 + }, + { + "first": 65, + "second": 41, + "amount": -1 + }, + { + "first": 65, + "second": 42, + "amount": -4 + }, + { + "first": 65, + "second": 48, + "amount": -1 + }, + { + "first": 65, + "second": 49, + "amount": 0 + }, + { + "first": 65, + "second": 53, + "amount": 0 + }, + { + "first": 65, + "second": 54, + "amount": -1 + }, + { + "first": 65, + "second": 55, + "amount": -3 + }, + { + "first": 65, + "second": 56, + "amount": -1 + }, + { + "first": 65, + "second": 57, + "amount": -2 + }, + { + "first": 65, + "second": 64, + "amount": -2 + }, + { + "first": 65, + "second": 66, + "amount": -1 + }, + { + "first": 65, + "second": 67, + "amount": -2 + }, + { + "first": 65, + "second": 68, + "amount": -1 + }, + { + "first": 65, + "second": 69, + "amount": -1 + }, + { + "first": 65, + "second": 70, + "amount": -1 + }, + { + "first": 65, + "second": 71, + "amount": -2 + }, + { + "first": 65, + "second": 72, + "amount": -1 + }, + { + "first": 65, + "second": 73, + "amount": -1 + }, + { + "first": 65, + "second": 74, + "amount": 0 + }, + { + "first": 65, + "second": 75, + "amount": -1 + }, + { + "first": 65, + "second": 76, + "amount": -1 + }, + { + "first": 65, + "second": 77, + "amount": -1 + }, + { + "first": 65, + "second": 78, + "amount": -1 + }, + { + "first": 65, + "second": 79, + "amount": -2 + }, + { + "first": 65, + "second": 80, + "amount": -1 + }, + { + "first": 65, + "second": 81, + "amount": -2 + }, + { + "first": 65, + "second": 82, + "amount": -1 + }, + { + "first": 65, + "second": 83, + "amount": -1 + }, + { + "first": 65, + "second": 84, + "amount": -5 + }, + { + "first": 65, + "second": 85, + "amount": -1 + }, + { + "first": 65, + "second": 86, + "amount": -5 + }, + { + "first": 65, + "second": 87, + "amount": -2 + }, + { + "first": 65, + "second": 88, + "amount": -1 + }, + { + "first": 65, + "second": 89, + "amount": -5 + }, + { + "first": 65, + "second": 92, + "amount": -4 + }, + { + "first": 65, + "second": 97, + "amount": 0 + }, + { + "first": 65, + "second": 99, + "amount": 0 + }, + { + "first": 65, + "second": 100, + "amount": 0 + }, + { + "first": 65, + "second": 101, + "amount": 0 + }, + { + "first": 65, + "second": 102, + "amount": 0 + }, + { + "first": 65, + "second": 103, + "amount": 0 + }, + { + "first": 65, + "second": 106, + "amount": 3 + }, + { + "first": 65, + "second": 111, + "amount": 0 + }, + { + "first": 65, + "second": 113, + "amount": 0 + }, + { + "first": 65, + "second": 115, + "amount": 0 + }, + { + "first": 65, + "second": 116, + "amount": -1 + }, + { + "first": 65, + "second": 117, + "amount": 0 + }, + { + "first": 65, + "second": 118, + "amount": -2 + }, + { + "first": 65, + "second": 119, + "amount": -2 + }, + { + "first": 65, + "second": 121, + "amount": -2 + }, + { + "first": 65, + "second": 169, + "amount": -2 + }, + { + "first": 65, + "second": 171, + "amount": 0 + }, + { + "first": 65, + "second": 174, + "amount": -2 + }, + { + "first": 65, + "second": 199, + "amount": -2 + }, + { + "first": 65, + "second": 200, + "amount": -1 + }, + { + "first": 65, + "second": 201, + "amount": -1 + }, + { + "first": 65, + "second": 202, + "amount": -1 + }, + { + "first": 65, + "second": 203, + "amount": -1 + }, + { + "first": 65, + "second": 204, + "amount": -1 + }, + { + "first": 65, + "second": 205, + "amount": -1 + }, + { + "first": 65, + "second": 206, + "amount": -1 + }, + { + "first": 65, + "second": 207, + "amount": -1 + }, + { + "first": 65, + "second": 209, + "amount": -1 + }, + { + "first": 65, + "second": 210, + "amount": -2 + }, + { + "first": 65, + "second": 211, + "amount": -2 + }, + { + "first": 65, + "second": 212, + "amount": -2 + }, + { + "first": 65, + "second": 213, + "amount": -2 + }, + { + "first": 65, + "second": 214, + "amount": -2 + }, + { + "first": 65, + "second": 216, + "amount": -2 + }, + { + "first": 65, + "second": 217, + "amount": -1 + }, + { + "first": 65, + "second": 218, + "amount": -1 + }, + { + "first": 65, + "second": 219, + "amount": -1 + }, + { + "first": 65, + "second": 220, + "amount": -1 + }, + { + "first": 65, + "second": 221, + "amount": -5 + }, + { + "first": 65, + "second": 222, + "amount": -1 + }, + { + "first": 65, + "second": 230, + "amount": 0 + }, + { + "first": 65, + "second": 231, + "amount": 0 + }, + { + "first": 65, + "second": 232, + "amount": 0 + }, + { + "first": 65, + "second": 233, + "amount": 0 + }, + { + "first": 65, + "second": 234, + "amount": 0 + }, + { + "first": 65, + "second": 235, + "amount": 0 + }, + { + "first": 65, + "second": 240, + "amount": 0 + }, + { + "first": 65, + "second": 242, + "amount": 0 + }, + { + "first": 65, + "second": 243, + "amount": 0 + }, + { + "first": 65, + "second": 244, + "amount": 0 + }, + { + "first": 65, + "second": 245, + "amount": 0 + }, + { + "first": 65, + "second": 246, + "amount": 0 + }, + { + "first": 65, + "second": 248, + "amount": 0 + }, + { + "first": 65, + "second": 250, + "amount": 0 + }, + { + "first": 65, + "second": 338, + "amount": -2 + }, + { + "first": 65, + "second": 339, + "amount": 0 + }, + { + "first": 65, + "second": 376, + "amount": -5 + }, + { + "first": 66, + "second": 44, + "amount": 0 + }, + { + "first": 66, + "second": 46, + "amount": 0 + }, + { + "first": 66, + "second": 47, + "amount": -1 + }, + { + "first": 66, + "second": 48, + "amount": -1 + }, + { + "first": 66, + "second": 50, + "amount": -1 + }, + { + "first": 66, + "second": 55, + "amount": -1 + }, + { + "first": 66, + "second": 57, + "amount": -1 + }, + { + "first": 66, + "second": 64, + "amount": 0 + }, + { + "first": 66, + "second": 65, + "amount": -1 + }, + { + "first": 66, + "second": 67, + "amount": 0 + }, + { + "first": 66, + "second": 71, + "amount": 0 + }, + { + "first": 66, + "second": 79, + "amount": 0 + }, + { + "first": 66, + "second": 81, + "amount": 0 + }, + { + "first": 66, + "second": 84, + "amount": 0 + }, + { + "first": 66, + "second": 85, + "amount": 0 + }, + { + "first": 66, + "second": 86, + "amount": -1 + }, + { + "first": 66, + "second": 87, + "amount": -1 + }, + { + "first": 66, + "second": 88, + "amount": -1 + }, + { + "first": 66, + "second": 89, + "amount": -1 + }, + { + "first": 66, + "second": 92, + "amount": -1 + }, + { + "first": 66, + "second": 102, + "amount": 0 + }, + { + "first": 66, + "second": 116, + "amount": 0 + }, + { + "first": 66, + "second": 118, + "amount": 0 + }, + { + "first": 66, + "second": 119, + "amount": 0 + }, + { + "first": 66, + "second": 120, + "amount": -1 + }, + { + "first": 66, + "second": 121, + "amount": 0 + }, + { + "first": 66, + "second": 125, + "amount": 0 + }, + { + "first": 66, + "second": 169, + "amount": 0 + }, + { + "first": 66, + "second": 174, + "amount": 0 + }, + { + "first": 66, + "second": 192, + "amount": -1 + }, + { + "first": 66, + "second": 193, + "amount": -1 + }, + { + "first": 66, + "second": 194, + "amount": -1 + }, + { + "first": 66, + "second": 195, + "amount": -1 + }, + { + "first": 66, + "second": 196, + "amount": -1 + }, + { + "first": 66, + "second": 197, + "amount": -1 + }, + { + "first": 66, + "second": 199, + "amount": 0 + }, + { + "first": 66, + "second": 210, + "amount": 0 + }, + { + "first": 66, + "second": 211, + "amount": 0 + }, + { + "first": 66, + "second": 212, + "amount": 0 + }, + { + "first": 66, + "second": 213, + "amount": 0 + }, + { + "first": 66, + "second": 214, + "amount": 0 + }, + { + "first": 66, + "second": 216, + "amount": 0 + }, + { + "first": 66, + "second": 217, + "amount": 0 + }, + { + "first": 66, + "second": 218, + "amount": 0 + }, + { + "first": 66, + "second": 219, + "amount": 0 + }, + { + "first": 66, + "second": 220, + "amount": 0 + }, + { + "first": 66, + "second": 221, + "amount": -1 + }, + { + "first": 66, + "second": 338, + "amount": 0 + }, + { + "first": 66, + "second": 376, + "amount": -1 + }, + { + "first": 67, + "second": 47, + "amount": -1 + }, + { + "first": 67, + "second": 64, + "amount": 0 + }, + { + "first": 67, + "second": 65, + "amount": -1 + }, + { + "first": 67, + "second": 67, + "amount": 0 + }, + { + "first": 67, + "second": 71, + "amount": 0 + }, + { + "first": 67, + "second": 74, + "amount": -1 + }, + { + "first": 67, + "second": 79, + "amount": 0 + }, + { + "first": 67, + "second": 81, + "amount": 0 + }, + { + "first": 67, + "second": 84, + "amount": 0 + }, + { + "first": 67, + "second": 86, + "amount": 0 + }, + { + "first": 67, + "second": 87, + "amount": 0 + }, + { + "first": 67, + "second": 88, + "amount": -1 + }, + { + "first": 67, + "second": 89, + "amount": 0 + }, + { + "first": 67, + "second": 90, + "amount": -1 + }, + { + "first": 67, + "second": 99, + "amount": 0 + }, + { + "first": 67, + "second": 100, + "amount": 0 + }, + { + "first": 67, + "second": 101, + "amount": 0 + }, + { + "first": 67, + "second": 103, + "amount": 0 + }, + { + "first": 67, + "second": 111, + "amount": 0 + }, + { + "first": 67, + "second": 113, + "amount": 0 + }, + { + "first": 67, + "second": 115, + "amount": 0 + }, + { + "first": 67, + "second": 118, + "amount": 0 + }, + { + "first": 67, + "second": 119, + "amount": 0 + }, + { + "first": 67, + "second": 120, + "amount": -1 + }, + { + "first": 67, + "second": 121, + "amount": 0 + }, + { + "first": 67, + "second": 122, + "amount": 0 + }, + { + "first": 67, + "second": 169, + "amount": 0 + }, + { + "first": 67, + "second": 171, + "amount": 0 + }, + { + "first": 67, + "second": 174, + "amount": 0 + }, + { + "first": 67, + "second": 187, + "amount": 0 + }, + { + "first": 67, + "second": 192, + "amount": -1 + }, + { + "first": 67, + "second": 193, + "amount": -1 + }, + { + "first": 67, + "second": 194, + "amount": -1 + }, + { + "first": 67, + "second": 195, + "amount": -1 + }, + { + "first": 67, + "second": 196, + "amount": -1 + }, + { + "first": 67, + "second": 197, + "amount": -1 + }, + { + "first": 67, + "second": 199, + "amount": 0 + }, + { + "first": 67, + "second": 210, + "amount": 0 + }, + { + "first": 67, + "second": 211, + "amount": 0 + }, + { + "first": 67, + "second": 212, + "amount": 0 + }, + { + "first": 67, + "second": 213, + "amount": 0 + }, + { + "first": 67, + "second": 214, + "amount": 0 + }, + { + "first": 67, + "second": 216, + "amount": 0 + }, + { + "first": 67, + "second": 221, + "amount": 0 + }, + { + "first": 67, + "second": 231, + "amount": 0 + }, + { + "first": 67, + "second": 248, + "amount": 0 + }, + { + "first": 67, + "second": 338, + "amount": 0 + }, + { + "first": 67, + "second": 339, + "amount": 0 + }, + { + "first": 67, + "second": 376, + "amount": 0 + }, + { + "first": 68, + "second": 44, + "amount": -1 + }, + { + "first": 68, + "second": 46, + "amount": -1 + }, + { + "first": 68, + "second": 47, + "amount": -1 + }, + { + "first": 68, + "second": 65, + "amount": -2 + }, + { + "first": 68, + "second": 74, + "amount": 0 + }, + { + "first": 68, + "second": 84, + "amount": -2 + }, + { + "first": 68, + "second": 86, + "amount": -3 + }, + { + "first": 68, + "second": 87, + "amount": -1 + }, + { + "first": 68, + "second": 88, + "amount": -1 + }, + { + "first": 68, + "second": 89, + "amount": -3 + }, + { + "first": 68, + "second": 90, + "amount": -1 + }, + { + "first": 68, + "second": 97, + "amount": 0 + }, + { + "first": 68, + "second": 99, + "amount": 0 + }, + { + "first": 68, + "second": 100, + "amount": 0 + }, + { + "first": 68, + "second": 101, + "amount": 0 + }, + { + "first": 68, + "second": 103, + "amount": 0 + }, + { + "first": 68, + "second": 111, + "amount": 0 + }, + { + "first": 68, + "second": 113, + "amount": 0 + }, + { + "first": 68, + "second": 115, + "amount": 0 + }, + { + "first": 68, + "second": 118, + "amount": 0 + }, + { + "first": 68, + "second": 119, + "amount": 0 + }, + { + "first": 68, + "second": 120, + "amount": 0 + }, + { + "first": 68, + "second": 121, + "amount": 0 + }, + { + "first": 68, + "second": 122, + "amount": 0 + }, + { + "first": 68, + "second": 192, + "amount": -2 + }, + { + "first": 68, + "second": 193, + "amount": -2 + }, + { + "first": 68, + "second": 194, + "amount": -2 + }, + { + "first": 68, + "second": 195, + "amount": -2 + }, + { + "first": 68, + "second": 196, + "amount": -2 + }, + { + "first": 68, + "second": 197, + "amount": -2 + }, + { + "first": 68, + "second": 221, + "amount": -3 + }, + { + "first": 68, + "second": 230, + "amount": 0 + }, + { + "first": 68, + "second": 231, + "amount": 0 + }, + { + "first": 68, + "second": 248, + "amount": 0 + }, + { + "first": 68, + "second": 339, + "amount": 0 + }, + { + "first": 68, + "second": 376, + "amount": -3 + }, + { + "first": 69, + "second": 38, + "amount": 0 + }, + { + "first": 69, + "second": 64, + "amount": -1 + }, + { + "first": 69, + "second": 67, + "amount": -1 + }, + { + "first": 69, + "second": 71, + "amount": -1 + }, + { + "first": 69, + "second": 74, + "amount": 0 + }, + { + "first": 69, + "second": 79, + "amount": -1 + }, + { + "first": 69, + "second": 81, + "amount": -1 + }, + { + "first": 69, + "second": 87, + "amount": 0 + }, + { + "first": 69, + "second": 97, + "amount": 0 + }, + { + "first": 69, + "second": 99, + "amount": 0 + }, + { + "first": 69, + "second": 100, + "amount": 0 + }, + { + "first": 69, + "second": 101, + "amount": 0 + }, + { + "first": 69, + "second": 102, + "amount": -1 + }, + { + "first": 69, + "second": 103, + "amount": 0 + }, + { + "first": 69, + "second": 106, + "amount": 1 + }, + { + "first": 69, + "second": 111, + "amount": 0 + }, + { + "first": 69, + "second": 113, + "amount": 0 + }, + { + "first": 69, + "second": 115, + "amount": 0 + }, + { + "first": 69, + "second": 116, + "amount": 0 + }, + { + "first": 69, + "second": 118, + "amount": -1 + }, + { + "first": 69, + "second": 119, + "amount": -1 + }, + { + "first": 69, + "second": 121, + "amount": -1 + }, + { + "first": 69, + "second": 169, + "amount": -1 + }, + { + "first": 69, + "second": 171, + "amount": -1 + }, + { + "first": 69, + "second": 174, + "amount": -1 + }, + { + "first": 69, + "second": 180, + "amount": 0 + }, + { + "first": 69, + "second": 199, + "amount": -1 + }, + { + "first": 69, + "second": 210, + "amount": -1 + }, + { + "first": 69, + "second": 211, + "amount": -1 + }, + { + "first": 69, + "second": 212, + "amount": -1 + }, + { + "first": 69, + "second": 213, + "amount": -1 + }, + { + "first": 69, + "second": 214, + "amount": -1 + }, + { + "first": 69, + "second": 216, + "amount": -1 + }, + { + "first": 69, + "second": 224, + "amount": 0 + }, + { + "first": 69, + "second": 225, + "amount": 0 + }, + { + "first": 69, + "second": 226, + "amount": 0 + }, + { + "first": 69, + "second": 227, + "amount": 0 + }, + { + "first": 69, + "second": 228, + "amount": 0 + }, + { + "first": 69, + "second": 229, + "amount": 0 + }, + { + "first": 69, + "second": 230, + "amount": 0 + }, + { + "first": 69, + "second": 231, + "amount": 0 + }, + { + "first": 69, + "second": 248, + "amount": 0 + }, + { + "first": 69, + "second": 338, + "amount": -1 + }, + { + "first": 69, + "second": 339, + "amount": 0 + }, + { + "first": 70, + "second": 38, + "amount": -3 + }, + { + "first": 70, + "second": 44, + "amount": -4 + }, + { + "first": 70, + "second": 46, + "amount": -4 + }, + { + "first": 70, + "second": 47, + "amount": -7 + }, + { + "first": 70, + "second": 48, + "amount": -1 + }, + { + "first": 70, + "second": 50, + "amount": 0 + }, + { + "first": 70, + "second": 52, + "amount": -4 + }, + { + "first": 70, + "second": 54, + "amount": -1 + }, + { + "first": 70, + "second": 55, + "amount": 0 + }, + { + "first": 70, + "second": 56, + "amount": -1 + }, + { + "first": 70, + "second": 58, + "amount": -1 + }, + { + "first": 70, + "second": 59, + "amount": -1 + }, + { + "first": 70, + "second": 64, + "amount": -1 + }, + { + "first": 70, + "second": 65, + "amount": -6 + }, + { + "first": 70, + "second": 67, + "amount": -1 + }, + { + "first": 70, + "second": 71, + "amount": -1 + }, + { + "first": 70, + "second": 74, + "amount": -7 + }, + { + "first": 70, + "second": 79, + "amount": -1 + }, + { + "first": 70, + "second": 81, + "amount": -1 + }, + { + "first": 70, + "second": 83, + "amount": 0 + }, + { + "first": 70, + "second": 86, + "amount": 0 + }, + { + "first": 70, + "second": 89, + "amount": 0 + }, + { + "first": 70, + "second": 97, + "amount": -2 + }, + { + "first": 70, + "second": 99, + "amount": -2 + }, + { + "first": 70, + "second": 100, + "amount": -2 + }, + { + "first": 70, + "second": 101, + "amount": -2 + }, + { + "first": 70, + "second": 102, + "amount": 0 + }, + { + "first": 70, + "second": 103, + "amount": -2 + }, + { + "first": 70, + "second": 109, + "amount": -2 + }, + { + "first": 70, + "second": 110, + "amount": -2 + }, + { + "first": 70, + "second": 111, + "amount": -2 + }, + { + "first": 70, + "second": 112, + "amount": -2 + }, + { + "first": 70, + "second": 113, + "amount": -2 + }, + { + "first": 70, + "second": 114, + "amount": -2 + }, + { + "first": 70, + "second": 115, + "amount": -2 + }, + { + "first": 70, + "second": 116, + "amount": 0 + }, + { + "first": 70, + "second": 117, + "amount": -2 + }, + { + "first": 70, + "second": 118, + "amount": -1 + }, + { + "first": 70, + "second": 119, + "amount": -1 + }, + { + "first": 70, + "second": 120, + "amount": -2 + }, + { + "first": 70, + "second": 121, + "amount": -1 + }, + { + "first": 70, + "second": 122, + "amount": -1 + }, + { + "first": 70, + "second": 169, + "amount": -1 + }, + { + "first": 70, + "second": 171, + "amount": -1 + }, + { + "first": 70, + "second": 174, + "amount": -1 + }, + { + "first": 70, + "second": 180, + "amount": -1 + }, + { + "first": 70, + "second": 181, + "amount": -2 + }, + { + "first": 70, + "second": 192, + "amount": -6 + }, + { + "first": 70, + "second": 193, + "amount": -6 + }, + { + "first": 70, + "second": 194, + "amount": -6 + }, + { + "first": 70, + "second": 195, + "amount": -6 + }, + { + "first": 70, + "second": 196, + "amount": -6 + }, + { + "first": 70, + "second": 197, + "amount": -6 + }, + { + "first": 70, + "second": 199, + "amount": -1 + }, + { + "first": 70, + "second": 210, + "amount": -1 + }, + { + "first": 70, + "second": 211, + "amount": -1 + }, + { + "first": 70, + "second": 212, + "amount": -1 + }, + { + "first": 70, + "second": 213, + "amount": -1 + }, + { + "first": 70, + "second": 214, + "amount": -1 + }, + { + "first": 70, + "second": 216, + "amount": -1 + }, + { + "first": 70, + "second": 221, + "amount": 0 + }, + { + "first": 70, + "second": 224, + "amount": -1 + }, + { + "first": 70, + "second": 225, + "amount": -1 + }, + { + "first": 70, + "second": 226, + "amount": -1 + }, + { + "first": 70, + "second": 227, + "amount": -1 + }, + { + "first": 70, + "second": 228, + "amount": -1 + }, + { + "first": 70, + "second": 229, + "amount": -1 + }, + { + "first": 70, + "second": 230, + "amount": -2 + }, + { + "first": 70, + "second": 231, + "amount": -2 + }, + { + "first": 70, + "second": 232, + "amount": -1 + }, + { + "first": 70, + "second": 233, + "amount": -1 + }, + { + "first": 70, + "second": 234, + "amount": -1 + }, + { + "first": 70, + "second": 235, + "amount": -1 + }, + { + "first": 70, + "second": 236, + "amount": 2 + }, + { + "first": 70, + "second": 237, + "amount": 2 + }, + { + "first": 70, + "second": 238, + "amount": 2 + }, + { + "first": 70, + "second": 239, + "amount": 2 + }, + { + "first": 70, + "second": 240, + "amount": -1 + }, + { + "first": 70, + "second": 242, + "amount": -1 + }, + { + "first": 70, + "second": 243, + "amount": -1 + }, + { + "first": 70, + "second": 244, + "amount": -1 + }, + { + "first": 70, + "second": 245, + "amount": -1 + }, + { + "first": 70, + "second": 246, + "amount": -1 + }, + { + "first": 70, + "second": 248, + "amount": -2 + }, + { + "first": 70, + "second": 249, + "amount": -1 + }, + { + "first": 70, + "second": 250, + "amount": -2 + }, + { + "first": 70, + "second": 251, + "amount": -1 + }, + { + "first": 70, + "second": 252, + "amount": -1 + }, + { + "first": 70, + "second": 338, + "amount": -1 + }, + { + "first": 70, + "second": 339, + "amount": -2 + }, + { + "first": 70, + "second": 376, + "amount": 0 + }, + { + "first": 71, + "second": 47, + "amount": -1 + }, + { + "first": 71, + "second": 51, + "amount": 0 + }, + { + "first": 71, + "second": 63, + "amount": 0 + }, + { + "first": 71, + "second": 65, + "amount": -1 + }, + { + "first": 71, + "second": 74, + "amount": 0 + }, + { + "first": 71, + "second": 86, + "amount": 0 + }, + { + "first": 71, + "second": 88, + "amount": 0 + }, + { + "first": 71, + "second": 89, + "amount": 0 + }, + { + "first": 71, + "second": 102, + "amount": 0 + }, + { + "first": 71, + "second": 115, + "amount": 0 + }, + { + "first": 71, + "second": 118, + "amount": -1 + }, + { + "first": 71, + "second": 119, + "amount": -1 + }, + { + "first": 71, + "second": 120, + "amount": -1 + }, + { + "first": 71, + "second": 121, + "amount": -1 + }, + { + "first": 71, + "second": 122, + "amount": 0 + }, + { + "first": 71, + "second": 192, + "amount": -1 + }, + { + "first": 71, + "second": 193, + "amount": -1 + }, + { + "first": 71, + "second": 194, + "amount": -1 + }, + { + "first": 71, + "second": 195, + "amount": -1 + }, + { + "first": 71, + "second": 196, + "amount": -1 + }, + { + "first": 71, + "second": 197, + "amount": -1 + }, + { + "first": 71, + "second": 221, + "amount": 0 + }, + { + "first": 71, + "second": 376, + "amount": 0 + }, + { + "first": 72, + "second": 65, + "amount": -1 + }, + { + "first": 72, + "second": 74, + "amount": 0 + }, + { + "first": 72, + "second": 86, + "amount": -1 + }, + { + "first": 72, + "second": 87, + "amount": -1 + }, + { + "first": 72, + "second": 89, + "amount": -1 + }, + { + "first": 72, + "second": 192, + "amount": -1 + }, + { + "first": 72, + "second": 193, + "amount": -1 + }, + { + "first": 72, + "second": 194, + "amount": -1 + }, + { + "first": 72, + "second": 195, + "amount": -1 + }, + { + "first": 72, + "second": 196, + "amount": -1 + }, + { + "first": 72, + "second": 197, + "amount": -1 + }, + { + "first": 72, + "second": 221, + "amount": -1 + }, + { + "first": 72, + "second": 376, + "amount": -1 + }, + { + "first": 73, + "second": 65, + "amount": -1 + }, + { + "first": 73, + "second": 74, + "amount": 0 + }, + { + "first": 73, + "second": 86, + "amount": -1 + }, + { + "first": 73, + "second": 87, + "amount": -1 + }, + { + "first": 73, + "second": 89, + "amount": -1 + }, + { + "first": 73, + "second": 192, + "amount": -1 + }, + { + "first": 73, + "second": 193, + "amount": -1 + }, + { + "first": 73, + "second": 194, + "amount": -1 + }, + { + "first": 73, + "second": 195, + "amount": -1 + }, + { + "first": 73, + "second": 196, + "amount": -1 + }, + { + "first": 73, + "second": 197, + "amount": -1 + }, + { + "first": 73, + "second": 221, + "amount": -1 + }, + { + "first": 73, + "second": 376, + "amount": -1 + }, + { + "first": 74, + "second": 44, + "amount": -2 + }, + { + "first": 74, + "second": 46, + "amount": -2 + }, + { + "first": 74, + "second": 47, + "amount": -3 + }, + { + "first": 74, + "second": 65, + "amount": -2 + }, + { + "first": 74, + "second": 88, + "amount": 0 + }, + { + "first": 74, + "second": 192, + "amount": -2 + }, + { + "first": 74, + "second": 193, + "amount": -2 + }, + { + "first": 74, + "second": 194, + "amount": -2 + }, + { + "first": 74, + "second": 195, + "amount": -2 + }, + { + "first": 74, + "second": 196, + "amount": -2 + }, + { + "first": 74, + "second": 197, + "amount": -2 + }, + { + "first": 75, + "second": 38, + "amount": -1 + }, + { + "first": 75, + "second": 48, + "amount": -1 + }, + { + "first": 75, + "second": 49, + "amount": 0 + }, + { + "first": 75, + "second": 51, + "amount": -1 + }, + { + "first": 75, + "second": 52, + "amount": 0 + }, + { + "first": 75, + "second": 53, + "amount": 0 + }, + { + "first": 75, + "second": 54, + "amount": -1 + }, + { + "first": 75, + "second": 55, + "amount": -1 + }, + { + "first": 75, + "second": 56, + "amount": -1 + }, + { + "first": 75, + "second": 57, + "amount": -2 + }, + { + "first": 75, + "second": 63, + "amount": -2 + }, + { + "first": 75, + "second": 64, + "amount": -2 + }, + { + "first": 75, + "second": 67, + "amount": -2 + }, + { + "first": 75, + "second": 71, + "amount": -2 + }, + { + "first": 75, + "second": 74, + "amount": 0 + }, + { + "first": 75, + "second": 79, + "amount": -2 + }, + { + "first": 75, + "second": 81, + "amount": -2 + }, + { + "first": 75, + "second": 83, + "amount": -1 + }, + { + "first": 75, + "second": 84, + "amount": -1 + }, + { + "first": 75, + "second": 85, + "amount": -1 + }, + { + "first": 75, + "second": 86, + "amount": -1 + }, + { + "first": 75, + "second": 87, + "amount": -1 + }, + { + "first": 75, + "second": 89, + "amount": -1 + }, + { + "first": 75, + "second": 97, + "amount": 0 + }, + { + "first": 75, + "second": 99, + "amount": -1 + }, + { + "first": 75, + "second": 100, + "amount": -1 + }, + { + "first": 75, + "second": 101, + "amount": -1 + }, + { + "first": 75, + "second": 102, + "amount": 0 + }, + { + "first": 75, + "second": 103, + "amount": -1 + }, + { + "first": 75, + "second": 111, + "amount": -1 + }, + { + "first": 75, + "second": 113, + "amount": -1 + }, + { + "first": 75, + "second": 115, + "amount": -1 + }, + { + "first": 75, + "second": 116, + "amount": 0 + }, + { + "first": 75, + "second": 117, + "amount": 0 + }, + { + "first": 75, + "second": 118, + "amount": -2 + }, + { + "first": 75, + "second": 119, + "amount": -2 + }, + { + "first": 75, + "second": 121, + "amount": -2 + }, + { + "first": 75, + "second": 169, + "amount": -2 + }, + { + "first": 75, + "second": 174, + "amount": -2 + }, + { + "first": 75, + "second": 199, + "amount": -2 + }, + { + "first": 75, + "second": 210, + "amount": -2 + }, + { + "first": 75, + "second": 211, + "amount": -2 + }, + { + "first": 75, + "second": 212, + "amount": -2 + }, + { + "first": 75, + "second": 213, + "amount": -2 + }, + { + "first": 75, + "second": 214, + "amount": -2 + }, + { + "first": 75, + "second": 216, + "amount": -2 + }, + { + "first": 75, + "second": 217, + "amount": -1 + }, + { + "first": 75, + "second": 218, + "amount": -1 + }, + { + "first": 75, + "second": 219, + "amount": -1 + }, + { + "first": 75, + "second": 220, + "amount": -1 + }, + { + "first": 75, + "second": 221, + "amount": -1 + }, + { + "first": 75, + "second": 230, + "amount": 0 + }, + { + "first": 75, + "second": 231, + "amount": -1 + }, + { + "first": 75, + "second": 248, + "amount": -1 + }, + { + "first": 75, + "second": 250, + "amount": 0 + }, + { + "first": 75, + "second": 338, + "amount": -2 + }, + { + "first": 75, + "second": 339, + "amount": -1 + }, + { + "first": 75, + "second": 376, + "amount": -1 + }, + { + "first": 76, + "second": 42, + "amount": -5 + }, + { + "first": 76, + "second": 48, + "amount": 0 + }, + { + "first": 76, + "second": 52, + "amount": 1 + }, + { + "first": 76, + "second": 55, + "amount": -2 + }, + { + "first": 76, + "second": 57, + "amount": -1 + }, + { + "first": 76, + "second": 63, + "amount": -2 + }, + { + "first": 76, + "second": 64, + "amount": -1 + }, + { + "first": 76, + "second": 65, + "amount": 0 + }, + { + "first": 76, + "second": 67, + "amount": -1 + }, + { + "first": 76, + "second": 71, + "amount": -1 + }, + { + "first": 76, + "second": 74, + "amount": 1 + }, + { + "first": 76, + "second": 79, + "amount": -1 + }, + { + "first": 76, + "second": 81, + "amount": -1 + }, + { + "first": 76, + "second": 84, + "amount": -4 + }, + { + "first": 76, + "second": 86, + "amount": -4 + }, + { + "first": 76, + "second": 89, + "amount": -4 + }, + { + "first": 76, + "second": 90, + "amount": 1 + }, + { + "first": 76, + "second": 92, + "amount": -2 + }, + { + "first": 76, + "second": 102, + "amount": 0 + }, + { + "first": 76, + "second": 116, + "amount": 0 + }, + { + "first": 76, + "second": 118, + "amount": -2 + }, + { + "first": 76, + "second": 119, + "amount": -1 + }, + { + "first": 76, + "second": 121, + "amount": -2 + }, + { + "first": 76, + "second": 169, + "amount": -1 + }, + { + "first": 76, + "second": 174, + "amount": -1 + }, + { + "first": 76, + "second": 192, + "amount": 0 + }, + { + "first": 76, + "second": 193, + "amount": 0 + }, + { + "first": 76, + "second": 194, + "amount": 0 + }, + { + "first": 76, + "second": 195, + "amount": 0 + }, + { + "first": 76, + "second": 196, + "amount": 0 + }, + { + "first": 76, + "second": 197, + "amount": 0 + }, + { + "first": 76, + "second": 198, + "amount": 2 + }, + { + "first": 76, + "second": 199, + "amount": -1 + }, + { + "first": 76, + "second": 210, + "amount": -1 + }, + { + "first": 76, + "second": 211, + "amount": -1 + }, + { + "first": 76, + "second": 212, + "amount": -1 + }, + { + "first": 76, + "second": 213, + "amount": -1 + }, + { + "first": 76, + "second": 214, + "amount": -1 + }, + { + "first": 76, + "second": 216, + "amount": -1 + }, + { + "first": 76, + "second": 221, + "amount": -4 + }, + { + "first": 76, + "second": 338, + "amount": -1 + }, + { + "first": 76, + "second": 376, + "amount": -4 + }, + { + "first": 77, + "second": 65, + "amount": -1 + }, + { + "first": 77, + "second": 74, + "amount": 0 + }, + { + "first": 77, + "second": 86, + "amount": -1 + }, + { + "first": 77, + "second": 87, + "amount": -1 + }, + { + "first": 77, + "second": 89, + "amount": -1 + }, + { + "first": 77, + "second": 192, + "amount": -1 + }, + { + "first": 77, + "second": 193, + "amount": -1 + }, + { + "first": 77, + "second": 194, + "amount": -1 + }, + { + "first": 77, + "second": 195, + "amount": -1 + }, + { + "first": 77, + "second": 196, + "amount": -1 + }, + { + "first": 77, + "second": 197, + "amount": -1 + }, + { + "first": 77, + "second": 221, + "amount": -1 + }, + { + "first": 77, + "second": 376, + "amount": -1 + }, + { + "first": 78, + "second": 65, + "amount": -1 + }, + { + "first": 78, + "second": 74, + "amount": 0 + }, + { + "first": 78, + "second": 86, + "amount": -1 + }, + { + "first": 78, + "second": 87, + "amount": -1 + }, + { + "first": 78, + "second": 89, + "amount": -1 + }, + { + "first": 78, + "second": 192, + "amount": -1 + }, + { + "first": 78, + "second": 193, + "amount": -1 + }, + { + "first": 78, + "second": 194, + "amount": -1 + }, + { + "first": 78, + "second": 195, + "amount": -1 + }, + { + "first": 78, + "second": 196, + "amount": -1 + }, + { + "first": 78, + "second": 197, + "amount": -1 + }, + { + "first": 78, + "second": 221, + "amount": -1 + }, + { + "first": 78, + "second": 376, + "amount": -1 + }, + { + "first": 79, + "second": 44, + "amount": -1 + }, + { + "first": 79, + "second": 46, + "amount": -1 + }, + { + "first": 79, + "second": 47, + "amount": -1 + }, + { + "first": 79, + "second": 65, + "amount": -2 + }, + { + "first": 79, + "second": 74, + "amount": 0 + }, + { + "first": 79, + "second": 84, + "amount": -2 + }, + { + "first": 79, + "second": 86, + "amount": -3 + }, + { + "first": 79, + "second": 87, + "amount": -1 + }, + { + "first": 79, + "second": 88, + "amount": -1 + }, + { + "first": 79, + "second": 89, + "amount": -3 + }, + { + "first": 79, + "second": 90, + "amount": -1 + }, + { + "first": 79, + "second": 97, + "amount": 0 + }, + { + "first": 79, + "second": 99, + "amount": 0 + }, + { + "first": 79, + "second": 100, + "amount": 0 + }, + { + "first": 79, + "second": 101, + "amount": 0 + }, + { + "first": 79, + "second": 103, + "amount": 0 + }, + { + "first": 79, + "second": 111, + "amount": 0 + }, + { + "first": 79, + "second": 113, + "amount": 0 + }, + { + "first": 79, + "second": 115, + "amount": 0 + }, + { + "first": 79, + "second": 118, + "amount": 0 + }, + { + "first": 79, + "second": 119, + "amount": 0 + }, + { + "first": 79, + "second": 120, + "amount": 0 + }, + { + "first": 79, + "second": 121, + "amount": 0 + }, + { + "first": 79, + "second": 122, + "amount": 0 + }, + { + "first": 79, + "second": 192, + "amount": -2 + }, + { + "first": 79, + "second": 193, + "amount": -2 + }, + { + "first": 79, + "second": 194, + "amount": -2 + }, + { + "first": 79, + "second": 195, + "amount": -2 + }, + { + "first": 79, + "second": 196, + "amount": -2 + }, + { + "first": 79, + "second": 197, + "amount": -2 + }, + { + "first": 79, + "second": 221, + "amount": -3 + }, + { + "first": 79, + "second": 230, + "amount": 0 + }, + { + "first": 79, + "second": 231, + "amount": 0 + }, + { + "first": 79, + "second": 248, + "amount": 0 + }, + { + "first": 79, + "second": 339, + "amount": 0 + }, + { + "first": 79, + "second": 376, + "amount": -3 + }, + { + "first": 80, + "second": 38, + "amount": -2 + }, + { + "first": 80, + "second": 41, + "amount": -1 + }, + { + "first": 80, + "second": 44, + "amount": -6 + }, + { + "first": 80, + "second": 46, + "amount": -6 + }, + { + "first": 80, + "second": 47, + "amount": -5 + }, + { + "first": 80, + "second": 50, + "amount": 0 + }, + { + "first": 80, + "second": 51, + "amount": -1 + }, + { + "first": 80, + "second": 52, + "amount": -1 + }, + { + "first": 80, + "second": 54, + "amount": -1 + }, + { + "first": 80, + "second": 56, + "amount": 0 + }, + { + "first": 80, + "second": 64, + "amount": 0 + }, + { + "first": 80, + "second": 65, + "amount": -5 + }, + { + "first": 80, + "second": 67, + "amount": 0 + }, + { + "first": 80, + "second": 71, + "amount": 0 + }, + { + "first": 80, + "second": 74, + "amount": -6 + }, + { + "first": 80, + "second": 79, + "amount": 0 + }, + { + "first": 80, + "second": 81, + "amount": 0 + }, + { + "first": 80, + "second": 83, + "amount": -1 + }, + { + "first": 80, + "second": 84, + "amount": 0 + }, + { + "first": 80, + "second": 85, + "amount": 0 + }, + { + "first": 80, + "second": 86, + "amount": -1 + }, + { + "first": 80, + "second": 87, + "amount": -1 + }, + { + "first": 80, + "second": 88, + "amount": -2 + }, + { + "first": 80, + "second": 89, + "amount": -1 + }, + { + "first": 80, + "second": 90, + "amount": -1 + }, + { + "first": 80, + "second": 97, + "amount": -1 + }, + { + "first": 80, + "second": 99, + "amount": -1 + }, + { + "first": 80, + "second": 100, + "amount": -1 + }, + { + "first": 80, + "second": 101, + "amount": -1 + }, + { + "first": 80, + "second": 102, + "amount": 0 + }, + { + "first": 80, + "second": 103, + "amount": -1 + }, + { + "first": 80, + "second": 111, + "amount": -1 + }, + { + "first": 80, + "second": 113, + "amount": -1 + }, + { + "first": 80, + "second": 115, + "amount": 0 + }, + { + "first": 80, + "second": 116, + "amount": 1 + }, + { + "first": 80, + "second": 117, + "amount": 0 + }, + { + "first": 80, + "second": 118, + "amount": 0 + }, + { + "first": 80, + "second": 119, + "amount": 0 + }, + { + "first": 80, + "second": 120, + "amount": 0 + }, + { + "first": 80, + "second": 121, + "amount": 0 + }, + { + "first": 80, + "second": 122, + "amount": 0 + }, + { + "first": 80, + "second": 169, + "amount": 0 + }, + { + "first": 80, + "second": 171, + "amount": -1 + }, + { + "first": 80, + "second": 174, + "amount": 0 + }, + { + "first": 80, + "second": 180, + "amount": -1 + }, + { + "first": 80, + "second": 192, + "amount": -5 + }, + { + "first": 80, + "second": 193, + "amount": -5 + }, + { + "first": 80, + "second": 194, + "amount": -5 + }, + { + "first": 80, + "second": 195, + "amount": -5 + }, + { + "first": 80, + "second": 196, + "amount": -5 + }, + { + "first": 80, + "second": 197, + "amount": -5 + }, + { + "first": 80, + "second": 198, + "amount": -5 + }, + { + "first": 80, + "second": 199, + "amount": 0 + }, + { + "first": 80, + "second": 210, + "amount": 0 + }, + { + "first": 80, + "second": 211, + "amount": 0 + }, + { + "first": 80, + "second": 212, + "amount": 0 + }, + { + "first": 80, + "second": 213, + "amount": 0 + }, + { + "first": 80, + "second": 214, + "amount": 0 + }, + { + "first": 80, + "second": 216, + "amount": 0 + }, + { + "first": 80, + "second": 217, + "amount": 0 + }, + { + "first": 80, + "second": 218, + "amount": 0 + }, + { + "first": 80, + "second": 219, + "amount": 0 + }, + { + "first": 80, + "second": 220, + "amount": 0 + }, + { + "first": 80, + "second": 221, + "amount": -1 + }, + { + "first": 80, + "second": 224, + "amount": -1 + }, + { + "first": 80, + "second": 225, + "amount": -1 + }, + { + "first": 80, + "second": 226, + "amount": -1 + }, + { + "first": 80, + "second": 227, + "amount": -1 + }, + { + "first": 80, + "second": 228, + "amount": -1 + }, + { + "first": 80, + "second": 229, + "amount": -1 + }, + { + "first": 80, + "second": 230, + "amount": -1 + }, + { + "first": 80, + "second": 231, + "amount": -1 + }, + { + "first": 80, + "second": 236, + "amount": 3 + }, + { + "first": 80, + "second": 237, + "amount": 3 + }, + { + "first": 80, + "second": 238, + "amount": 3 + }, + { + "first": 80, + "second": 239, + "amount": 3 + }, + { + "first": 80, + "second": 248, + "amount": -1 + }, + { + "first": 80, + "second": 250, + "amount": 0 + }, + { + "first": 80, + "second": 253, + "amount": 0 + }, + { + "first": 80, + "second": 255, + "amount": 0 + }, + { + "first": 80, + "second": 338, + "amount": 0 + }, + { + "first": 80, + "second": 339, + "amount": -1 + }, + { + "first": 80, + "second": 376, + "amount": -1 + }, + { + "first": 81, + "second": 44, + "amount": -1 + }, + { + "first": 81, + "second": 46, + "amount": -1 + }, + { + "first": 81, + "second": 47, + "amount": -1 + }, + { + "first": 81, + "second": 65, + "amount": -2 + }, + { + "first": 81, + "second": 74, + "amount": 0 + }, + { + "first": 81, + "second": 84, + "amount": -2 + }, + { + "first": 81, + "second": 86, + "amount": -3 + }, + { + "first": 81, + "second": 87, + "amount": -1 + }, + { + "first": 81, + "second": 88, + "amount": -1 + }, + { + "first": 81, + "second": 89, + "amount": -3 + }, + { + "first": 81, + "second": 90, + "amount": -1 + }, + { + "first": 81, + "second": 97, + "amount": 0 + }, + { + "first": 81, + "second": 99, + "amount": 0 + }, + { + "first": 81, + "second": 100, + "amount": 0 + }, + { + "first": 81, + "second": 101, + "amount": 0 + }, + { + "first": 81, + "second": 103, + "amount": 0 + }, + { + "first": 81, + "second": 111, + "amount": 0 + }, + { + "first": 81, + "second": 113, + "amount": 0 + }, + { + "first": 81, + "second": 115, + "amount": 0 + }, + { + "first": 81, + "second": 118, + "amount": 0 + }, + { + "first": 81, + "second": 119, + "amount": 0 + }, + { + "first": 81, + "second": 120, + "amount": 0 + }, + { + "first": 81, + "second": 121, + "amount": 0 + }, + { + "first": 81, + "second": 122, + "amount": 0 + }, + { + "first": 81, + "second": 192, + "amount": -2 + }, + { + "first": 81, + "second": 193, + "amount": -2 + }, + { + "first": 81, + "second": 194, + "amount": -2 + }, + { + "first": 81, + "second": 195, + "amount": -2 + }, + { + "first": 81, + "second": 196, + "amount": -2 + }, + { + "first": 81, + "second": 197, + "amount": -2 + }, + { + "first": 81, + "second": 221, + "amount": -3 + }, + { + "first": 81, + "second": 230, + "amount": 0 + }, + { + "first": 81, + "second": 231, + "amount": 0 + }, + { + "first": 81, + "second": 248, + "amount": 0 + }, + { + "first": 81, + "second": 339, + "amount": 0 + }, + { + "first": 81, + "second": 376, + "amount": -3 + }, + { + "first": 82, + "second": 38, + "amount": -1 + }, + { + "first": 82, + "second": 44, + "amount": 0 + }, + { + "first": 82, + "second": 46, + "amount": 0 + }, + { + "first": 82, + "second": 48, + "amount": 0 + }, + { + "first": 82, + "second": 50, + "amount": 0 + }, + { + "first": 82, + "second": 51, + "amount": 0 + }, + { + "first": 82, + "second": 52, + "amount": 0 + }, + { + "first": 82, + "second": 54, + "amount": 0 + }, + { + "first": 82, + "second": 58, + "amount": 0 + }, + { + "first": 82, + "second": 59, + "amount": 0 + }, + { + "first": 82, + "second": 64, + "amount": 0 + }, + { + "first": 82, + "second": 65, + "amount": -1 + }, + { + "first": 82, + "second": 67, + "amount": 0 + }, + { + "first": 82, + "second": 71, + "amount": 0 + }, + { + "first": 82, + "second": 74, + "amount": -1 + }, + { + "first": 82, + "second": 79, + "amount": 0 + }, + { + "first": 82, + "second": 81, + "amount": 0 + }, + { + "first": 82, + "second": 83, + "amount": 0 + }, + { + "first": 82, + "second": 84, + "amount": -1 + }, + { + "first": 82, + "second": 85, + "amount": 0 + }, + { + "first": 82, + "second": 86, + "amount": 0 + }, + { + "first": 82, + "second": 87, + "amount": 0 + }, + { + "first": 82, + "second": 88, + "amount": -1 + }, + { + "first": 82, + "second": 89, + "amount": -1 + }, + { + "first": 82, + "second": 90, + "amount": 0 + }, + { + "first": 82, + "second": 97, + "amount": -1 + }, + { + "first": 82, + "second": 99, + "amount": -1 + }, + { + "first": 82, + "second": 100, + "amount": -1 + }, + { + "first": 82, + "second": 101, + "amount": -1 + }, + { + "first": 82, + "second": 103, + "amount": -1 + }, + { + "first": 82, + "second": 111, + "amount": -1 + }, + { + "first": 82, + "second": 113, + "amount": -1 + }, + { + "first": 82, + "second": 115, + "amount": 0 + }, + { + "first": 82, + "second": 117, + "amount": 0 + }, + { + "first": 82, + "second": 118, + "amount": 0 + }, + { + "first": 82, + "second": 119, + "amount": 0 + }, + { + "first": 82, + "second": 120, + "amount": 0 + }, + { + "first": 82, + "second": 121, + "amount": 0 + }, + { + "first": 82, + "second": 122, + "amount": 0 + }, + { + "first": 82, + "second": 169, + "amount": 0 + }, + { + "first": 82, + "second": 171, + "amount": -1 + }, + { + "first": 82, + "second": 174, + "amount": 0 + }, + { + "first": 82, + "second": 180, + "amount": -1 + }, + { + "first": 82, + "second": 192, + "amount": -1 + }, + { + "first": 82, + "second": 193, + "amount": -1 + }, + { + "first": 82, + "second": 194, + "amount": -1 + }, + { + "first": 82, + "second": 195, + "amount": -1 + }, + { + "first": 82, + "second": 196, + "amount": -1 + }, + { + "first": 82, + "second": 197, + "amount": -1 + }, + { + "first": 82, + "second": 199, + "amount": 0 + }, + { + "first": 82, + "second": 210, + "amount": 0 + }, + { + "first": 82, + "second": 211, + "amount": 0 + }, + { + "first": 82, + "second": 212, + "amount": 0 + }, + { + "first": 82, + "second": 213, + "amount": 0 + }, + { + "first": 82, + "second": 214, + "amount": 0 + }, + { + "first": 82, + "second": 216, + "amount": 0 + }, + { + "first": 82, + "second": 217, + "amount": 0 + }, + { + "first": 82, + "second": 218, + "amount": 0 + }, + { + "first": 82, + "second": 219, + "amount": 0 + }, + { + "first": 82, + "second": 220, + "amount": 0 + }, + { + "first": 82, + "second": 221, + "amount": -1 + }, + { + "first": 82, + "second": 224, + "amount": -1 + }, + { + "first": 82, + "second": 225, + "amount": -1 + }, + { + "first": 82, + "second": 226, + "amount": -1 + }, + { + "first": 82, + "second": 227, + "amount": -1 + }, + { + "first": 82, + "second": 228, + "amount": -1 + }, + { + "first": 82, + "second": 229, + "amount": -1 + }, + { + "first": 82, + "second": 230, + "amount": -1 + }, + { + "first": 82, + "second": 231, + "amount": -1 + }, + { + "first": 82, + "second": 248, + "amount": -1 + }, + { + "first": 82, + "second": 250, + "amount": 0 + }, + { + "first": 82, + "second": 338, + "amount": 0 + }, + { + "first": 82, + "second": 339, + "amount": -1 + }, + { + "first": 82, + "second": 376, + "amount": -1 + }, + { + "first": 83, + "second": 47, + "amount": -1 + }, + { + "first": 83, + "second": 55, + "amount": 0 + }, + { + "first": 83, + "second": 65, + "amount": -1 + }, + { + "first": 83, + "second": 86, + "amount": -2 + }, + { + "first": 83, + "second": 88, + "amount": -1 + }, + { + "first": 83, + "second": 89, + "amount": -2 + }, + { + "first": 83, + "second": 90, + "amount": 0 + }, + { + "first": 83, + "second": 99, + "amount": 0 + }, + { + "first": 83, + "second": 100, + "amount": 0 + }, + { + "first": 83, + "second": 101, + "amount": 0 + }, + { + "first": 83, + "second": 102, + "amount": 0 + }, + { + "first": 83, + "second": 103, + "amount": 0 + }, + { + "first": 83, + "second": 111, + "amount": 0 + }, + { + "first": 83, + "second": 113, + "amount": 0 + }, + { + "first": 83, + "second": 118, + "amount": -1 + }, + { + "first": 83, + "second": 119, + "amount": 0 + }, + { + "first": 83, + "second": 120, + "amount": -1 + }, + { + "first": 83, + "second": 121, + "amount": -1 + }, + { + "first": 83, + "second": 192, + "amount": -1 + }, + { + "first": 83, + "second": 193, + "amount": -1 + }, + { + "first": 83, + "second": 194, + "amount": -1 + }, + { + "first": 83, + "second": 195, + "amount": -1 + }, + { + "first": 83, + "second": 196, + "amount": -1 + }, + { + "first": 83, + "second": 197, + "amount": -1 + }, + { + "first": 83, + "second": 221, + "amount": -2 + }, + { + "first": 83, + "second": 231, + "amount": 0 + }, + { + "first": 83, + "second": 248, + "amount": 0 + }, + { + "first": 83, + "second": 339, + "amount": 0 + }, + { + "first": 83, + "second": 376, + "amount": -2 + }, + { + "first": 84, + "second": 32, + "amount": -1 + }, + { + "first": 84, + "second": 38, + "amount": -3 + }, + { + "first": 84, + "second": 41, + "amount": 0 + }, + { + "first": 84, + "second": 44, + "amount": -4 + }, + { + "first": 84, + "second": 45, + "amount": -2 + }, + { + "first": 84, + "second": 46, + "amount": -4 + }, + { + "first": 84, + "second": 47, + "amount": -5 + }, + { + "first": 84, + "second": 48, + "amount": -1 + }, + { + "first": 84, + "second": 49, + "amount": 0 + }, + { + "first": 84, + "second": 50, + "amount": -1 + }, + { + "first": 84, + "second": 51, + "amount": -1 + }, + { + "first": 84, + "second": 52, + "amount": -3 + }, + { + "first": 84, + "second": 54, + "amount": -3 + }, + { + "first": 84, + "second": 55, + "amount": 0 + }, + { + "first": 84, + "second": 56, + "amount": -1 + }, + { + "first": 84, + "second": 58, + "amount": -1 + }, + { + "first": 84, + "second": 59, + "amount": -1 + }, + { + "first": 84, + "second": 64, + "amount": -2 + }, + { + "first": 84, + "second": 65, + "amount": -5 + }, + { + "first": 84, + "second": 67, + "amount": -2 + }, + { + "first": 84, + "second": 71, + "amount": -2 + }, + { + "first": 84, + "second": 74, + "amount": -5 + }, + { + "first": 84, + "second": 79, + "amount": -2 + }, + { + "first": 84, + "second": 81, + "amount": -2 + }, + { + "first": 84, + "second": 88, + "amount": 0 + }, + { + "first": 84, + "second": 92, + "amount": 0 + }, + { + "first": 84, + "second": 97, + "amount": -3 + }, + { + "first": 84, + "second": 99, + "amount": -4 + }, + { + "first": 84, + "second": 100, + "amount": -4 + }, + { + "first": 84, + "second": 101, + "amount": -4 + }, + { + "first": 84, + "second": 102, + "amount": -1 + }, + { + "first": 84, + "second": 103, + "amount": -4 + }, + { + "first": 84, + "second": 109, + "amount": -2 + }, + { + "first": 84, + "second": 110, + "amount": -2 + }, + { + "first": 84, + "second": 111, + "amount": -4 + }, + { + "first": 84, + "second": 112, + "amount": -2 + }, + { + "first": 84, + "second": 113, + "amount": -4 + }, + { + "first": 84, + "second": 114, + "amount": -2 + }, + { + "first": 84, + "second": 115, + "amount": -3 + }, + { + "first": 84, + "second": 116, + "amount": -1 + }, + { + "first": 84, + "second": 117, + "amount": -3 + }, + { + "first": 84, + "second": 118, + "amount": -2 + }, + { + "first": 84, + "second": 119, + "amount": -2 + }, + { + "first": 84, + "second": 120, + "amount": -2 + }, + { + "first": 84, + "second": 121, + "amount": -2 + }, + { + "first": 84, + "second": 122, + "amount": -2 + }, + { + "first": 84, + "second": 169, + "amount": -2 + }, + { + "first": 84, + "second": 171, + "amount": -3 + }, + { + "first": 84, + "second": 174, + "amount": -2 + }, + { + "first": 84, + "second": 180, + "amount": -2 + }, + { + "first": 84, + "second": 181, + "amount": -2 + }, + { + "first": 84, + "second": 187, + "amount": -2 + }, + { + "first": 84, + "second": 192, + "amount": -5 + }, + { + "first": 84, + "second": 193, + "amount": -5 + }, + { + "first": 84, + "second": 194, + "amount": -5 + }, + { + "first": 84, + "second": 195, + "amount": -5 + }, + { + "first": 84, + "second": 196, + "amount": -5 + }, + { + "first": 84, + "second": 197, + "amount": -5 + }, + { + "first": 84, + "second": 198, + "amount": -4 + }, + { + "first": 84, + "second": 199, + "amount": -2 + }, + { + "first": 84, + "second": 210, + "amount": -2 + }, + { + "first": 84, + "second": 211, + "amount": -2 + }, + { + "first": 84, + "second": 212, + "amount": -2 + }, + { + "first": 84, + "second": 213, + "amount": -2 + }, + { + "first": 84, + "second": 214, + "amount": -2 + }, + { + "first": 84, + "second": 216, + "amount": -2 + }, + { + "first": 84, + "second": 224, + "amount": -2 + }, + { + "first": 84, + "second": 225, + "amount": -2 + }, + { + "first": 84, + "second": 226, + "amount": -2 + }, + { + "first": 84, + "second": 227, + "amount": -2 + }, + { + "first": 84, + "second": 228, + "amount": -2 + }, + { + "first": 84, + "second": 229, + "amount": -2 + }, + { + "first": 84, + "second": 230, + "amount": -3 + }, + { + "first": 84, + "second": 231, + "amount": -4 + }, + { + "first": 84, + "second": 232, + "amount": -3 + }, + { + "first": 84, + "second": 233, + "amount": -3 + }, + { + "first": 84, + "second": 234, + "amount": -3 + }, + { + "first": 84, + "second": 235, + "amount": -3 + }, + { + "first": 84, + "second": 236, + "amount": 3 + }, + { + "first": 84, + "second": 237, + "amount": 3 + }, + { + "first": 84, + "second": 238, + "amount": 3 + }, + { + "first": 84, + "second": 239, + "amount": 3 + }, + { + "first": 84, + "second": 240, + "amount": -3 + }, + { + "first": 84, + "second": 242, + "amount": -3 + }, + { + "first": 84, + "second": 243, + "amount": -3 + }, + { + "first": 84, + "second": 244, + "amount": -3 + }, + { + "first": 84, + "second": 245, + "amount": -3 + }, + { + "first": 84, + "second": 246, + "amount": -3 + }, + { + "first": 84, + "second": 248, + "amount": -4 + }, + { + "first": 84, + "second": 249, + "amount": -1 + }, + { + "first": 84, + "second": 250, + "amount": -3 + }, + { + "first": 84, + "second": 251, + "amount": -1 + }, + { + "first": 84, + "second": 252, + "amount": -1 + }, + { + "first": 84, + "second": 253, + "amount": -1 + }, + { + "first": 84, + "second": 255, + "amount": -1 + }, + { + "first": 84, + "second": 338, + "amount": -2 + }, + { + "first": 84, + "second": 339, + "amount": -4 + }, + { + "first": 85, + "second": 44, + "amount": -2 + }, + { + "first": 85, + "second": 46, + "amount": -2 + }, + { + "first": 85, + "second": 47, + "amount": -2 + }, + { + "first": 85, + "second": 65, + "amount": -1 + }, + { + "first": 85, + "second": 88, + "amount": 0 + }, + { + "first": 85, + "second": 115, + "amount": 0 + }, + { + "first": 85, + "second": 120, + "amount": 0 + }, + { + "first": 85, + "second": 192, + "amount": -1 + }, + { + "first": 85, + "second": 193, + "amount": -1 + }, + { + "first": 85, + "second": 194, + "amount": -1 + }, + { + "first": 85, + "second": 195, + "amount": -1 + }, + { + "first": 85, + "second": 196, + "amount": -1 + }, + { + "first": 85, + "second": 197, + "amount": -1 + }, + { + "first": 86, + "second": 32, + "amount": -2 + }, + { + "first": 86, + "second": 38, + "amount": -3 + }, + { + "first": 86, + "second": 41, + "amount": -1 + }, + { + "first": 86, + "second": 44, + "amount": -5 + }, + { + "first": 86, + "second": 45, + "amount": -1 + }, + { + "first": 86, + "second": 46, + "amount": -5 + }, + { + "first": 86, + "second": 47, + "amount": -6 + }, + { + "first": 86, + "second": 48, + "amount": -2 + }, + { + "first": 86, + "second": 50, + "amount": -1 + }, + { + "first": 86, + "second": 51, + "amount": -1 + }, + { + "first": 86, + "second": 52, + "amount": -4 + }, + { + "first": 86, + "second": 53, + "amount": -1 + }, + { + "first": 86, + "second": 54, + "amount": -3 + }, + { + "first": 86, + "second": 55, + "amount": 0 + }, + { + "first": 86, + "second": 56, + "amount": -3 + }, + { + "first": 86, + "second": 57, + "amount": -2 + }, + { + "first": 86, + "second": 58, + "amount": -4 + }, + { + "first": 86, + "second": 59, + "amount": -4 + }, + { + "first": 86, + "second": 64, + "amount": -3 + }, + { + "first": 86, + "second": 65, + "amount": -5 + }, + { + "first": 86, + "second": 66, + "amount": -1 + }, + { + "first": 86, + "second": 67, + "amount": -3 + }, + { + "first": 86, + "second": 68, + "amount": -1 + }, + { + "first": 86, + "second": 69, + "amount": -1 + }, + { + "first": 86, + "second": 70, + "amount": -1 + }, + { + "first": 86, + "second": 71, + "amount": -3 + }, + { + "first": 86, + "second": 72, + "amount": -1 + }, + { + "first": 86, + "second": 73, + "amount": -1 + }, + { + "first": 86, + "second": 74, + "amount": -6 + }, + { + "first": 86, + "second": 75, + "amount": -1 + }, + { + "first": 86, + "second": 76, + "amount": -1 + }, + { + "first": 86, + "second": 77, + "amount": -1 + }, + { + "first": 86, + "second": 78, + "amount": -1 + }, + { + "first": 86, + "second": 79, + "amount": -3 + }, + { + "first": 86, + "second": 80, + "amount": -1 + }, + { + "first": 86, + "second": 81, + "amount": -3 + }, + { + "first": 86, + "second": 82, + "amount": -1 + }, + { + "first": 86, + "second": 83, + "amount": -1 + }, + { + "first": 86, + "second": 88, + "amount": -1 + }, + { + "first": 86, + "second": 97, + "amount": -4 + }, + { + "first": 86, + "second": 99, + "amount": -4 + }, + { + "first": 86, + "second": 100, + "amount": -4 + }, + { + "first": 86, + "second": 101, + "amount": -4 + }, + { + "first": 86, + "second": 102, + "amount": -1 + }, + { + "first": 86, + "second": 103, + "amount": -4 + }, + { + "first": 86, + "second": 109, + "amount": -1 + }, + { + "first": 86, + "second": 110, + "amount": -1 + }, + { + "first": 86, + "second": 111, + "amount": -4 + }, + { + "first": 86, + "second": 112, + "amount": -1 + }, + { + "first": 86, + "second": 113, + "amount": -4 + }, + { + "first": 86, + "second": 114, + "amount": -1 + }, + { + "first": 86, + "second": 115, + "amount": -3 + }, + { + "first": 86, + "second": 117, + "amount": -1 + }, + { + "first": 86, + "second": 118, + "amount": -2 + }, + { + "first": 86, + "second": 119, + "amount": -1 + }, + { + "first": 86, + "second": 120, + "amount": -3 + }, + { + "first": 86, + "second": 121, + "amount": -2 + }, + { + "first": 86, + "second": 122, + "amount": -2 + }, + { + "first": 86, + "second": 169, + "amount": -3 + }, + { + "first": 86, + "second": 171, + "amount": -4 + }, + { + "first": 86, + "second": 174, + "amount": -3 + }, + { + "first": 86, + "second": 180, + "amount": -3 + }, + { + "first": 86, + "second": 181, + "amount": -1 + }, + { + "first": 86, + "second": 187, + "amount": -1 + }, + { + "first": 86, + "second": 192, + "amount": -5 + }, + { + "first": 86, + "second": 193, + "amount": -5 + }, + { + "first": 86, + "second": 194, + "amount": -5 + }, + { + "first": 86, + "second": 195, + "amount": -5 + }, + { + "first": 86, + "second": 196, + "amount": -5 + }, + { + "first": 86, + "second": 197, + "amount": -5 + }, + { + "first": 86, + "second": 198, + "amount": -5 + }, + { + "first": 86, + "second": 199, + "amount": -3 + }, + { + "first": 86, + "second": 200, + "amount": -1 + }, + { + "first": 86, + "second": 201, + "amount": -1 + }, + { + "first": 86, + "second": 202, + "amount": -1 + }, + { + "first": 86, + "second": 203, + "amount": -1 + }, + { + "first": 86, + "second": 204, + "amount": -1 + }, + { + "first": 86, + "second": 205, + "amount": -1 + }, + { + "first": 86, + "second": 206, + "amount": -1 + }, + { + "first": 86, + "second": 207, + "amount": -1 + }, + { + "first": 86, + "second": 209, + "amount": -1 + }, + { + "first": 86, + "second": 210, + "amount": -3 + }, + { + "first": 86, + "second": 211, + "amount": -3 + }, + { + "first": 86, + "second": 212, + "amount": -3 + }, + { + "first": 86, + "second": 213, + "amount": -3 + }, + { + "first": 86, + "second": 214, + "amount": -3 + }, + { + "first": 86, + "second": 216, + "amount": -3 + }, + { + "first": 86, + "second": 222, + "amount": -1 + }, + { + "first": 86, + "second": 224, + "amount": -3 + }, + { + "first": 86, + "second": 225, + "amount": -3 + }, + { + "first": 86, + "second": 226, + "amount": -3 + }, + { + "first": 86, + "second": 227, + "amount": -3 + }, + { + "first": 86, + "second": 228, + "amount": -3 + }, + { + "first": 86, + "second": 229, + "amount": -3 + }, + { + "first": 86, + "second": 230, + "amount": -4 + }, + { + "first": 86, + "second": 231, + "amount": -4 + }, + { + "first": 86, + "second": 232, + "amount": -3 + }, + { + "first": 86, + "second": 233, + "amount": -3 + }, + { + "first": 86, + "second": 234, + "amount": -3 + }, + { + "first": 86, + "second": 235, + "amount": -3 + }, + { + "first": 86, + "second": 236, + "amount": 3 + }, + { + "first": 86, + "second": 237, + "amount": 3 + }, + { + "first": 86, + "second": 238, + "amount": 3 + }, + { + "first": 86, + "second": 239, + "amount": 3 + }, + { + "first": 86, + "second": 240, + "amount": -3 + }, + { + "first": 86, + "second": 241, + "amount": -1 + }, + { + "first": 86, + "second": 242, + "amount": -3 + }, + { + "first": 86, + "second": 243, + "amount": -3 + }, + { + "first": 86, + "second": 244, + "amount": -3 + }, + { + "first": 86, + "second": 245, + "amount": -3 + }, + { + "first": 86, + "second": 246, + "amount": -3 + }, + { + "first": 86, + "second": 248, + "amount": -4 + }, + { + "first": 86, + "second": 249, + "amount": -1 + }, + { + "first": 86, + "second": 250, + "amount": -1 + }, + { + "first": 86, + "second": 251, + "amount": -1 + }, + { + "first": 86, + "second": 252, + "amount": -1 + }, + { + "first": 86, + "second": 253, + "amount": -2 + }, + { + "first": 86, + "second": 255, + "amount": -2 + }, + { + "first": 86, + "second": 338, + "amount": -3 + }, + { + "first": 86, + "second": 339, + "amount": -4 + }, + { + "first": 87, + "second": 32, + "amount": 0 + }, + { + "first": 87, + "second": 41, + "amount": 0 + }, + { + "first": 87, + "second": 44, + "amount": -1 + }, + { + "first": 87, + "second": 46, + "amount": -1 + }, + { + "first": 87, + "second": 47, + "amount": -4 + }, + { + "first": 87, + "second": 48, + "amount": 0 + }, + { + "first": 87, + "second": 50, + "amount": 0 + }, + { + "first": 87, + "second": 51, + "amount": 0 + }, + { + "first": 87, + "second": 52, + "amount": -1 + }, + { + "first": 87, + "second": 53, + "amount": 0 + }, + { + "first": 87, + "second": 54, + "amount": 0 + }, + { + "first": 87, + "second": 56, + "amount": 0 + }, + { + "first": 87, + "second": 57, + "amount": 0 + }, + { + "first": 87, + "second": 58, + "amount": 0 + }, + { + "first": 87, + "second": 59, + "amount": 0 + }, + { + "first": 87, + "second": 63, + "amount": 0 + }, + { + "first": 87, + "second": 64, + "amount": -1 + }, + { + "first": 87, + "second": 65, + "amount": -2 + }, + { + "first": 87, + "second": 66, + "amount": -1 + }, + { + "first": 87, + "second": 67, + "amount": -1 + }, + { + "first": 87, + "second": 68, + "amount": -1 + }, + { + "first": 87, + "second": 69, + "amount": -1 + }, + { + "first": 87, + "second": 70, + "amount": -1 + }, + { + "first": 87, + "second": 71, + "amount": -1 + }, + { + "first": 87, + "second": 72, + "amount": -1 + }, + { + "first": 87, + "second": 73, + "amount": -1 + }, + { + "first": 87, + "second": 74, + "amount": -2 + }, + { + "first": 87, + "second": 75, + "amount": -1 + }, + { + "first": 87, + "second": 76, + "amount": -1 + }, + { + "first": 87, + "second": 77, + "amount": -1 + }, + { + "first": 87, + "second": 78, + "amount": -1 + }, + { + "first": 87, + "second": 79, + "amount": -1 + }, + { + "first": 87, + "second": 80, + "amount": -1 + }, + { + "first": 87, + "second": 81, + "amount": -1 + }, + { + "first": 87, + "second": 82, + "amount": -1 + }, + { + "first": 87, + "second": 88, + "amount": -1 + }, + { + "first": 87, + "second": 92, + "amount": 0 + }, + { + "first": 87, + "second": 97, + "amount": -2 + }, + { + "first": 87, + "second": 99, + "amount": -1 + }, + { + "first": 87, + "second": 100, + "amount": -1 + }, + { + "first": 87, + "second": 101, + "amount": -1 + }, + { + "first": 87, + "second": 102, + "amount": 0 + }, + { + "first": 87, + "second": 103, + "amount": -1 + }, + { + "first": 87, + "second": 109, + "amount": -1 + }, + { + "first": 87, + "second": 110, + "amount": -1 + }, + { + "first": 87, + "second": 111, + "amount": -1 + }, + { + "first": 87, + "second": 112, + "amount": -1 + }, + { + "first": 87, + "second": 113, + "amount": -1 + }, + { + "first": 87, + "second": 114, + "amount": -1 + }, + { + "first": 87, + "second": 118, + "amount": 0 + }, + { + "first": 87, + "second": 119, + "amount": -1 + }, + { + "first": 87, + "second": 120, + "amount": -1 + }, + { + "first": 87, + "second": 121, + "amount": 0 + }, + { + "first": 87, + "second": 122, + "amount": 0 + }, + { + "first": 87, + "second": 169, + "amount": -1 + }, + { + "first": 87, + "second": 171, + "amount": -1 + }, + { + "first": 87, + "second": 174, + "amount": -1 + }, + { + "first": 87, + "second": 180, + "amount": -3 + }, + { + "first": 87, + "second": 181, + "amount": -1 + }, + { + "first": 87, + "second": 192, + "amount": -2 + }, + { + "first": 87, + "second": 193, + "amount": -2 + }, + { + "first": 87, + "second": 194, + "amount": -2 + }, + { + "first": 87, + "second": 195, + "amount": -2 + }, + { + "first": 87, + "second": 196, + "amount": -2 + }, + { + "first": 87, + "second": 197, + "amount": -2 + }, + { + "first": 87, + "second": 198, + "amount": -2 + }, + { + "first": 87, + "second": 199, + "amount": -1 + }, + { + "first": 87, + "second": 200, + "amount": -1 + }, + { + "first": 87, + "second": 201, + "amount": -1 + }, + { + "first": 87, + "second": 202, + "amount": -1 + }, + { + "first": 87, + "second": 203, + "amount": -1 + }, + { + "first": 87, + "second": 204, + "amount": -1 + }, + { + "first": 87, + "second": 205, + "amount": -1 + }, + { + "first": 87, + "second": 206, + "amount": -1 + }, + { + "first": 87, + "second": 207, + "amount": -1 + }, + { + "first": 87, + "second": 209, + "amount": -1 + }, + { + "first": 87, + "second": 210, + "amount": -1 + }, + { + "first": 87, + "second": 211, + "amount": -1 + }, + { + "first": 87, + "second": 212, + "amount": -1 + }, + { + "first": 87, + "second": 213, + "amount": -1 + }, + { + "first": 87, + "second": 214, + "amount": -1 + }, + { + "first": 87, + "second": 216, + "amount": -1 + }, + { + "first": 87, + "second": 222, + "amount": -1 + }, + { + "first": 87, + "second": 224, + "amount": -3 + }, + { + "first": 87, + "second": 225, + "amount": -3 + }, + { + "first": 87, + "second": 226, + "amount": -3 + }, + { + "first": 87, + "second": 227, + "amount": -3 + }, + { + "first": 87, + "second": 228, + "amount": -3 + }, + { + "first": 87, + "second": 229, + "amount": -3 + }, + { + "first": 87, + "second": 230, + "amount": -2 + }, + { + "first": 87, + "second": 231, + "amount": -1 + }, + { + "first": 87, + "second": 236, + "amount": 3 + }, + { + "first": 87, + "second": 237, + "amount": 3 + }, + { + "first": 87, + "second": 238, + "amount": 3 + }, + { + "first": 87, + "second": 239, + "amount": 3 + }, + { + "first": 87, + "second": 248, + "amount": -1 + }, + { + "first": 87, + "second": 249, + "amount": 0 + }, + { + "first": 87, + "second": 251, + "amount": 0 + }, + { + "first": 87, + "second": 252, + "amount": 0 + }, + { + "first": 87, + "second": 338, + "amount": -1 + }, + { + "first": 87, + "second": 339, + "amount": -1 + }, + { + "first": 88, + "second": 38, + "amount": -1 + }, + { + "first": 88, + "second": 45, + "amount": -1 + }, + { + "first": 88, + "second": 48, + "amount": -1 + }, + { + "first": 88, + "second": 51, + "amount": 0 + }, + { + "first": 88, + "second": 52, + "amount": 0 + }, + { + "first": 88, + "second": 54, + "amount": -1 + }, + { + "first": 88, + "second": 56, + "amount": 0 + }, + { + "first": 88, + "second": 57, + "amount": -1 + }, + { + "first": 88, + "second": 63, + "amount": 0 + }, + { + "first": 88, + "second": 64, + "amount": -2 + }, + { + "first": 88, + "second": 65, + "amount": -1 + }, + { + "first": 88, + "second": 66, + "amount": 0 + }, + { + "first": 88, + "second": 67, + "amount": -2 + }, + { + "first": 88, + "second": 68, + "amount": 0 + }, + { + "first": 88, + "second": 69, + "amount": 0 + }, + { + "first": 88, + "second": 70, + "amount": 0 + }, + { + "first": 88, + "second": 71, + "amount": -2 + }, + { + "first": 88, + "second": 72, + "amount": 0 + }, + { + "first": 88, + "second": 73, + "amount": 0 + }, + { + "first": 88, + "second": 74, + "amount": -1 + }, + { + "first": 88, + "second": 75, + "amount": 0 + }, + { + "first": 88, + "second": 76, + "amount": 0 + }, + { + "first": 88, + "second": 77, + "amount": 0 + }, + { + "first": 88, + "second": 78, + "amount": 0 + }, + { + "first": 88, + "second": 79, + "amount": -2 + }, + { + "first": 88, + "second": 80, + "amount": 0 + }, + { + "first": 88, + "second": 81, + "amount": -2 + }, + { + "first": 88, + "second": 82, + "amount": 0 + }, + { + "first": 88, + "second": 83, + "amount": -1 + }, + { + "first": 88, + "second": 84, + "amount": 0 + }, + { + "first": 88, + "second": 85, + "amount": 0 + }, + { + "first": 88, + "second": 86, + "amount": -1 + }, + { + "first": 88, + "second": 87, + "amount": 0 + }, + { + "first": 88, + "second": 89, + "amount": -1 + }, + { + "first": 88, + "second": 90, + "amount": 0 + }, + { + "first": 88, + "second": 97, + "amount": -1 + }, + { + "first": 88, + "second": 98, + "amount": 0 + }, + { + "first": 88, + "second": 99, + "amount": -1 + }, + { + "first": 88, + "second": 100, + "amount": -1 + }, + { + "first": 88, + "second": 101, + "amount": -1 + }, + { + "first": 88, + "second": 102, + "amount": -1 + }, + { + "first": 88, + "second": 103, + "amount": -1 + }, + { + "first": 88, + "second": 104, + "amount": 0 + }, + { + "first": 88, + "second": 105, + "amount": 0 + }, + { + "first": 88, + "second": 106, + "amount": 0 + }, + { + "first": 88, + "second": 107, + "amount": 0 + }, + { + "first": 88, + "second": 108, + "amount": 0 + }, + { + "first": 88, + "second": 109, + "amount": 0 + }, + { + "first": 88, + "second": 110, + "amount": 0 + }, + { + "first": 88, + "second": 111, + "amount": -1 + }, + { + "first": 88, + "second": 112, + "amount": 0 + }, + { + "first": 88, + "second": 113, + "amount": -1 + }, + { + "first": 88, + "second": 114, + "amount": 0 + }, + { + "first": 88, + "second": 115, + "amount": -1 + }, + { + "first": 88, + "second": 116, + "amount": -1 + }, + { + "first": 88, + "second": 117, + "amount": -1 + }, + { + "first": 88, + "second": 118, + "amount": -1 + }, + { + "first": 88, + "second": 119, + "amount": -1 + }, + { + "first": 88, + "second": 120, + "amount": 0 + }, + { + "first": 88, + "second": 121, + "amount": -1 + }, + { + "first": 88, + "second": 122, + "amount": 0 + }, + { + "first": 88, + "second": 169, + "amount": -2 + }, + { + "first": 88, + "second": 171, + "amount": -1 + }, + { + "first": 88, + "second": 174, + "amount": -2 + }, + { + "first": 88, + "second": 181, + "amount": 0 + }, + { + "first": 88, + "second": 192, + "amount": -1 + }, + { + "first": 88, + "second": 193, + "amount": -1 + }, + { + "first": 88, + "second": 194, + "amount": -1 + }, + { + "first": 88, + "second": 195, + "amount": -1 + }, + { + "first": 88, + "second": 196, + "amount": -1 + }, + { + "first": 88, + "second": 197, + "amount": -1 + }, + { + "first": 88, + "second": 199, + "amount": -2 + }, + { + "first": 88, + "second": 200, + "amount": 0 + }, + { + "first": 88, + "second": 201, + "amount": 0 + }, + { + "first": 88, + "second": 202, + "amount": 0 + }, + { + "first": 88, + "second": 203, + "amount": 0 + }, + { + "first": 88, + "second": 204, + "amount": 0 + }, + { + "first": 88, + "second": 205, + "amount": 0 + }, + { + "first": 88, + "second": 206, + "amount": 0 + }, + { + "first": 88, + "second": 207, + "amount": 0 + }, + { + "first": 88, + "second": 209, + "amount": 0 + }, + { + "first": 88, + "second": 210, + "amount": -2 + }, + { + "first": 88, + "second": 211, + "amount": -2 + }, + { + "first": 88, + "second": 212, + "amount": -2 + }, + { + "first": 88, + "second": 213, + "amount": -2 + }, + { + "first": 88, + "second": 214, + "amount": -2 + }, + { + "first": 88, + "second": 216, + "amount": -2 + }, + { + "first": 88, + "second": 217, + "amount": 0 + }, + { + "first": 88, + "second": 218, + "amount": 0 + }, + { + "first": 88, + "second": 219, + "amount": 0 + }, + { + "first": 88, + "second": 220, + "amount": 0 + }, + { + "first": 88, + "second": 221, + "amount": -1 + }, + { + "first": 88, + "second": 222, + "amount": 0 + }, + { + "first": 88, + "second": 223, + "amount": 0 + }, + { + "first": 88, + "second": 230, + "amount": -1 + }, + { + "first": 88, + "second": 231, + "amount": -1 + }, + { + "first": 88, + "second": 248, + "amount": -1 + }, + { + "first": 88, + "second": 250, + "amount": -1 + }, + { + "first": 88, + "second": 254, + "amount": 0 + }, + { + "first": 88, + "second": 338, + "amount": -2 + }, + { + "first": 88, + "second": 339, + "amount": -1 + }, + { + "first": 88, + "second": 376, + "amount": -1 + }, + { + "first": 89, + "second": 32, + "amount": -2 + }, + { + "first": 89, + "second": 38, + "amount": -3 + }, + { + "first": 89, + "second": 41, + "amount": -1 + }, + { + "first": 89, + "second": 44, + "amount": -5 + }, + { + "first": 89, + "second": 45, + "amount": -1 + }, + { + "first": 89, + "second": 46, + "amount": -5 + }, + { + "first": 89, + "second": 47, + "amount": -6 + }, + { + "first": 89, + "second": 48, + "amount": -2 + }, + { + "first": 89, + "second": 50, + "amount": -1 + }, + { + "first": 89, + "second": 51, + "amount": -1 + }, + { + "first": 89, + "second": 52, + "amount": -4 + }, + { + "first": 89, + "second": 53, + "amount": -1 + }, + { + "first": 89, + "second": 54, + "amount": -3 + }, + { + "first": 89, + "second": 55, + "amount": 0 + }, + { + "first": 89, + "second": 56, + "amount": -3 + }, + { + "first": 89, + "second": 57, + "amount": -2 + }, + { + "first": 89, + "second": 58, + "amount": -4 + }, + { + "first": 89, + "second": 59, + "amount": -4 + }, + { + "first": 89, + "second": 64, + "amount": -3 + }, + { + "first": 89, + "second": 65, + "amount": -5 + }, + { + "first": 89, + "second": 66, + "amount": -1 + }, + { + "first": 89, + "second": 67, + "amount": -3 + }, + { + "first": 89, + "second": 68, + "amount": -1 + }, + { + "first": 89, + "second": 69, + "amount": -1 + }, + { + "first": 89, + "second": 70, + "amount": -1 + }, + { + "first": 89, + "second": 71, + "amount": -3 + }, + { + "first": 89, + "second": 72, + "amount": -1 + }, + { + "first": 89, + "second": 73, + "amount": -1 + }, + { + "first": 89, + "second": 74, + "amount": -6 + }, + { + "first": 89, + "second": 75, + "amount": -1 + }, + { + "first": 89, + "second": 76, + "amount": -1 + }, + { + "first": 89, + "second": 77, + "amount": -1 + }, + { + "first": 89, + "second": 78, + "amount": -1 + }, + { + "first": 89, + "second": 79, + "amount": -3 + }, + { + "first": 89, + "second": 80, + "amount": -1 + }, + { + "first": 89, + "second": 81, + "amount": -3 + }, + { + "first": 89, + "second": 82, + "amount": -1 + }, + { + "first": 89, + "second": 83, + "amount": -1 + }, + { + "first": 89, + "second": 88, + "amount": -1 + }, + { + "first": 89, + "second": 97, + "amount": -4 + }, + { + "first": 89, + "second": 99, + "amount": -4 + }, + { + "first": 89, + "second": 100, + "amount": -4 + }, + { + "first": 89, + "second": 101, + "amount": -4 + }, + { + "first": 89, + "second": 102, + "amount": -1 + }, + { + "first": 89, + "second": 103, + "amount": -4 + }, + { + "first": 89, + "second": 109, + "amount": -1 + }, + { + "first": 89, + "second": 110, + "amount": -1 + }, + { + "first": 89, + "second": 111, + "amount": -4 + }, + { + "first": 89, + "second": 112, + "amount": -1 + }, + { + "first": 89, + "second": 113, + "amount": -4 + }, + { + "first": 89, + "second": 114, + "amount": -1 + }, + { + "first": 89, + "second": 115, + "amount": -3 + }, + { + "first": 89, + "second": 117, + "amount": -1 + }, + { + "first": 89, + "second": 118, + "amount": -2 + }, + { + "first": 89, + "second": 119, + "amount": -1 + }, + { + "first": 89, + "second": 120, + "amount": -3 + }, + { + "first": 89, + "second": 121, + "amount": -2 + }, + { + "first": 89, + "second": 122, + "amount": -2 + }, + { + "first": 89, + "second": 169, + "amount": -3 + }, + { + "first": 89, + "second": 171, + "amount": -4 + }, + { + "first": 89, + "second": 174, + "amount": -3 + }, + { + "first": 89, + "second": 180, + "amount": -3 + }, + { + "first": 89, + "second": 181, + "amount": -1 + }, + { + "first": 89, + "second": 187, + "amount": -1 + }, + { + "first": 89, + "second": 192, + "amount": -5 + }, + { + "first": 89, + "second": 193, + "amount": -5 + }, + { + "first": 89, + "second": 194, + "amount": -5 + }, + { + "first": 89, + "second": 195, + "amount": -5 + }, + { + "first": 89, + "second": 196, + "amount": -5 + }, + { + "first": 89, + "second": 197, + "amount": -5 + }, + { + "first": 89, + "second": 198, + "amount": -5 + }, + { + "first": 89, + "second": 199, + "amount": -3 + }, + { + "first": 89, + "second": 200, + "amount": -1 + }, + { + "first": 89, + "second": 201, + "amount": -1 + }, + { + "first": 89, + "second": 202, + "amount": -1 + }, + { + "first": 89, + "second": 203, + "amount": -1 + }, + { + "first": 89, + "second": 204, + "amount": -1 + }, + { + "first": 89, + "second": 205, + "amount": -1 + }, + { + "first": 89, + "second": 206, + "amount": -1 + }, + { + "first": 89, + "second": 207, + "amount": -1 + }, + { + "first": 89, + "second": 209, + "amount": -1 + }, + { + "first": 89, + "second": 210, + "amount": -3 + }, + { + "first": 89, + "second": 211, + "amount": -3 + }, + { + "first": 89, + "second": 212, + "amount": -3 + }, + { + "first": 89, + "second": 213, + "amount": -3 + }, + { + "first": 89, + "second": 214, + "amount": -3 + }, + { + "first": 89, + "second": 216, + "amount": -3 + }, + { + "first": 89, + "second": 222, + "amount": -1 + }, + { + "first": 89, + "second": 224, + "amount": -3 + }, + { + "first": 89, + "second": 225, + "amount": -3 + }, + { + "first": 89, + "second": 226, + "amount": -3 + }, + { + "first": 89, + "second": 227, + "amount": -3 + }, + { + "first": 89, + "second": 228, + "amount": -3 + }, + { + "first": 89, + "second": 229, + "amount": -3 + }, + { + "first": 89, + "second": 230, + "amount": -4 + }, + { + "first": 89, + "second": 231, + "amount": -4 + }, + { + "first": 89, + "second": 232, + "amount": -3 + }, + { + "first": 89, + "second": 233, + "amount": -3 + }, + { + "first": 89, + "second": 234, + "amount": -3 + }, + { + "first": 89, + "second": 235, + "amount": -3 + }, + { + "first": 89, + "second": 236, + "amount": 3 + }, + { + "first": 89, + "second": 237, + "amount": 3 + }, + { + "first": 89, + "second": 238, + "amount": 3 + }, + { + "first": 89, + "second": 239, + "amount": 3 + }, + { + "first": 89, + "second": 240, + "amount": -3 + }, + { + "first": 89, + "second": 241, + "amount": -1 + }, + { + "first": 89, + "second": 242, + "amount": -3 + }, + { + "first": 89, + "second": 243, + "amount": -3 + }, + { + "first": 89, + "second": 244, + "amount": -3 + }, + { + "first": 89, + "second": 245, + "amount": -3 + }, + { + "first": 89, + "second": 246, + "amount": -3 + }, + { + "first": 89, + "second": 248, + "amount": -4 + }, + { + "first": 89, + "second": 249, + "amount": -1 + }, + { + "first": 89, + "second": 250, + "amount": -1 + }, + { + "first": 89, + "second": 251, + "amount": -1 + }, + { + "first": 89, + "second": 252, + "amount": -1 + }, + { + "first": 89, + "second": 253, + "amount": -2 + }, + { + "first": 89, + "second": 255, + "amount": -2 + }, + { + "first": 89, + "second": 338, + "amount": -3 + }, + { + "first": 89, + "second": 339, + "amount": -4 + }, + { + "first": 90, + "second": 54, + "amount": 0 + }, + { + "first": 90, + "second": 56, + "amount": 0 + }, + { + "first": 90, + "second": 64, + "amount": -1 + }, + { + "first": 90, + "second": 67, + "amount": -1 + }, + { + "first": 90, + "second": 71, + "amount": -1 + }, + { + "first": 90, + "second": 79, + "amount": -1 + }, + { + "first": 90, + "second": 81, + "amount": -1 + }, + { + "first": 90, + "second": 83, + "amount": 0 + }, + { + "first": 90, + "second": 99, + "amount": 0 + }, + { + "first": 90, + "second": 100, + "amount": 0 + }, + { + "first": 90, + "second": 101, + "amount": 0 + }, + { + "first": 90, + "second": 102, + "amount": 0 + }, + { + "first": 90, + "second": 103, + "amount": 0 + }, + { + "first": 90, + "second": 111, + "amount": 0 + }, + { + "first": 90, + "second": 113, + "amount": 0 + }, + { + "first": 90, + "second": 115, + "amount": 0 + }, + { + "first": 90, + "second": 116, + "amount": 0 + }, + { + "first": 90, + "second": 118, + "amount": 0 + }, + { + "first": 90, + "second": 119, + "amount": -1 + }, + { + "first": 90, + "second": 120, + "amount": 0 + }, + { + "first": 90, + "second": 121, + "amount": 0 + }, + { + "first": 90, + "second": 169, + "amount": -1 + }, + { + "first": 90, + "second": 174, + "amount": -1 + }, + { + "first": 90, + "second": 199, + "amount": -1 + }, + { + "first": 90, + "second": 210, + "amount": -1 + }, + { + "first": 90, + "second": 211, + "amount": -1 + }, + { + "first": 90, + "second": 212, + "amount": -1 + }, + { + "first": 90, + "second": 213, + "amount": -1 + }, + { + "first": 90, + "second": 214, + "amount": -1 + }, + { + "first": 90, + "second": 216, + "amount": -1 + }, + { + "first": 90, + "second": 231, + "amount": 0 + }, + { + "first": 90, + "second": 248, + "amount": 0 + }, + { + "first": 90, + "second": 338, + "amount": -1 + }, + { + "first": 90, + "second": 339, + "amount": 0 + }, + { + "first": 91, + "second": 74, + "amount": -1 + }, + { + "first": 91, + "second": 106, + "amount": 4 + }, + { + "first": 92, + "second": 74, + "amount": -1 + }, + { + "first": 92, + "second": 83, + "amount": -1 + }, + { + "first": 92, + "second": 84, + "amount": -3 + }, + { + "first": 92, + "second": 85, + "amount": -2 + }, + { + "first": 92, + "second": 86, + "amount": -3 + }, + { + "first": 92, + "second": 88, + "amount": -1 + }, + { + "first": 92, + "second": 89, + "amount": -3 + }, + { + "first": 92, + "second": 97, + "amount": -1 + }, + { + "first": 92, + "second": 106, + "amount": 2 + }, + { + "first": 92, + "second": 120, + "amount": 0 + }, + { + "first": 92, + "second": 217, + "amount": -2 + }, + { + "first": 92, + "second": 218, + "amount": -2 + }, + { + "first": 92, + "second": 219, + "amount": -2 + }, + { + "first": 92, + "second": 220, + "amount": -2 + }, + { + "first": 92, + "second": 221, + "amount": -3 + }, + { + "first": 92, + "second": 230, + "amount": -1 + }, + { + "first": 92, + "second": 376, + "amount": -3 + }, + { + "first": 95, + "second": 106, + "amount": 3 + }, + { + "first": 97, + "second": 55, + "amount": -1 + }, + { + "first": 97, + "second": 84, + "amount": -2 + }, + { + "first": 97, + "second": 86, + "amount": -3 + }, + { + "first": 97, + "second": 87, + "amount": -1 + }, + { + "first": 97, + "second": 89, + "amount": -3 + }, + { + "first": 97, + "second": 92, + "amount": -1 + }, + { + "first": 97, + "second": 106, + "amount": 1 + }, + { + "first": 97, + "second": 118, + "amount": 0 + }, + { + "first": 97, + "second": 119, + "amount": 0 + }, + { + "first": 97, + "second": 121, + "amount": 0 + }, + { + "first": 97, + "second": 221, + "amount": -3 + }, + { + "first": 97, + "second": 253, + "amount": 0 + }, + { + "first": 97, + "second": 255, + "amount": 0 + }, + { + "first": 97, + "second": 376, + "amount": -3 + }, + { + "first": 98, + "second": 42, + "amount": -2 + }, + { + "first": 98, + "second": 44, + "amount": -1 + }, + { + "first": 98, + "second": 46, + "amount": -1 + }, + { + "first": 98, + "second": 55, + "amount": -2 + }, + { + "first": 98, + "second": 64, + "amount": 0 + }, + { + "first": 98, + "second": 65, + "amount": 0 + }, + { + "first": 98, + "second": 67, + "amount": 0 + }, + { + "first": 98, + "second": 71, + "amount": 0 + }, + { + "first": 98, + "second": 79, + "amount": 0 + }, + { + "first": 98, + "second": 81, + "amount": 0 + }, + { + "first": 98, + "second": 83, + "amount": 0 + }, + { + "first": 98, + "second": 84, + "amount": -4 + }, + { + "first": 98, + "second": 86, + "amount": -4 + }, + { + "first": 98, + "second": 87, + "amount": -1 + }, + { + "first": 98, + "second": 88, + "amount": -2 + }, + { + "first": 98, + "second": 89, + "amount": -4 + }, + { + "first": 98, + "second": 90, + "amount": 0 + }, + { + "first": 98, + "second": 102, + "amount": 0 + }, + { + "first": 98, + "second": 115, + "amount": 0 + }, + { + "first": 98, + "second": 116, + "amount": 0 + }, + { + "first": 98, + "second": 118, + "amount": 0 + }, + { + "first": 98, + "second": 119, + "amount": 0 + }, + { + "first": 98, + "second": 120, + "amount": -1 + }, + { + "first": 98, + "second": 121, + "amount": 0 + }, + { + "first": 98, + "second": 122, + "amount": 0 + }, + { + "first": 98, + "second": 169, + "amount": 0 + }, + { + "first": 98, + "second": 174, + "amount": 0 + }, + { + "first": 98, + "second": 192, + "amount": 0 + }, + { + "first": 98, + "second": 193, + "amount": 0 + }, + { + "first": 98, + "second": 194, + "amount": 0 + }, + { + "first": 98, + "second": 195, + "amount": 0 + }, + { + "first": 98, + "second": 196, + "amount": 0 + }, + { + "first": 98, + "second": 197, + "amount": 0 + }, + { + "first": 98, + "second": 199, + "amount": 0 + }, + { + "first": 98, + "second": 210, + "amount": 0 + }, + { + "first": 98, + "second": 211, + "amount": 0 + }, + { + "first": 98, + "second": 212, + "amount": 0 + }, + { + "first": 98, + "second": 213, + "amount": 0 + }, + { + "first": 98, + "second": 214, + "amount": 0 + }, + { + "first": 98, + "second": 216, + "amount": 0 + }, + { + "first": 98, + "second": 221, + "amount": -4 + }, + { + "first": 98, + "second": 338, + "amount": 0 + }, + { + "first": 98, + "second": 376, + "amount": -4 + }, + { + "first": 99, + "second": 47, + "amount": -1 + }, + { + "first": 99, + "second": 64, + "amount": 0 + }, + { + "first": 99, + "second": 67, + "amount": 0 + }, + { + "first": 99, + "second": 71, + "amount": 0 + }, + { + "first": 99, + "second": 79, + "amount": 0 + }, + { + "first": 99, + "second": 81, + "amount": 0 + }, + { + "first": 99, + "second": 84, + "amount": -4 + }, + { + "first": 99, + "second": 85, + "amount": 0 + }, + { + "first": 99, + "second": 86, + "amount": -2 + }, + { + "first": 99, + "second": 87, + "amount": -1 + }, + { + "first": 99, + "second": 88, + "amount": -1 + }, + { + "first": 99, + "second": 89, + "amount": -2 + }, + { + "first": 99, + "second": 90, + "amount": 0 + }, + { + "first": 99, + "second": 92, + "amount": -1 + }, + { + "first": 99, + "second": 97, + "amount": 0 + }, + { + "first": 99, + "second": 99, + "amount": 0 + }, + { + "first": 99, + "second": 100, + "amount": 0 + }, + { + "first": 99, + "second": 101, + "amount": 0 + }, + { + "first": 99, + "second": 103, + "amount": 0 + }, + { + "first": 99, + "second": 111, + "amount": 0 + }, + { + "first": 99, + "second": 113, + "amount": 0 + }, + { + "first": 99, + "second": 115, + "amount": 0 + }, + { + "first": 99, + "second": 116, + "amount": 0 + }, + { + "first": 99, + "second": 117, + "amount": 0 + }, + { + "first": 99, + "second": 118, + "amount": 0 + }, + { + "first": 99, + "second": 119, + "amount": 0 + }, + { + "first": 99, + "second": 120, + "amount": -1 + }, + { + "first": 99, + "second": 121, + "amount": 0 + }, + { + "first": 99, + "second": 122, + "amount": 0 + }, + { + "first": 99, + "second": 169, + "amount": 0 + }, + { + "first": 99, + "second": 171, + "amount": 0 + }, + { + "first": 99, + "second": 174, + "amount": 0 + }, + { + "first": 99, + "second": 180, + "amount": 0 + }, + { + "first": 99, + "second": 187, + "amount": 0 + }, + { + "first": 99, + "second": 199, + "amount": 0 + }, + { + "first": 99, + "second": 210, + "amount": 0 + }, + { + "first": 99, + "second": 211, + "amount": 0 + }, + { + "first": 99, + "second": 212, + "amount": 0 + }, + { + "first": 99, + "second": 213, + "amount": 0 + }, + { + "first": 99, + "second": 214, + "amount": 0 + }, + { + "first": 99, + "second": 216, + "amount": 0 + }, + { + "first": 99, + "second": 217, + "amount": 0 + }, + { + "first": 99, + "second": 218, + "amount": 0 + }, + { + "first": 99, + "second": 219, + "amount": 0 + }, + { + "first": 99, + "second": 220, + "amount": 0 + }, + { + "first": 99, + "second": 221, + "amount": -2 + }, + { + "first": 99, + "second": 224, + "amount": 0 + }, + { + "first": 99, + "second": 225, + "amount": 0 + }, + { + "first": 99, + "second": 226, + "amount": 0 + }, + { + "first": 99, + "second": 227, + "amount": 0 + }, + { + "first": 99, + "second": 228, + "amount": 0 + }, + { + "first": 99, + "second": 229, + "amount": 0 + }, + { + "first": 99, + "second": 230, + "amount": 0 + }, + { + "first": 99, + "second": 231, + "amount": 0 + }, + { + "first": 99, + "second": 248, + "amount": 0 + }, + { + "first": 99, + "second": 249, + "amount": 0 + }, + { + "first": 99, + "second": 250, + "amount": 0 + }, + { + "first": 99, + "second": 251, + "amount": 0 + }, + { + "first": 99, + "second": 252, + "amount": 0 + }, + { + "first": 99, + "second": 338, + "amount": 0 + }, + { + "first": 99, + "second": 339, + "amount": 0 + }, + { + "first": 99, + "second": 376, + "amount": -2 + }, + { + "first": 100, + "second": 88, + "amount": 0 + }, + { + "first": 101, + "second": 42, + "amount": -2 + }, + { + "first": 101, + "second": 47, + "amount": -2 + }, + { + "first": 101, + "second": 64, + "amount": 0 + }, + { + "first": 101, + "second": 67, + "amount": 0 + }, + { + "first": 101, + "second": 71, + "amount": 0 + }, + { + "first": 101, + "second": 79, + "amount": 0 + }, + { + "first": 101, + "second": 81, + "amount": 0 + }, + { + "first": 101, + "second": 84, + "amount": -4 + }, + { + "first": 101, + "second": 86, + "amount": -2 + }, + { + "first": 101, + "second": 87, + "amount": -1 + }, + { + "first": 101, + "second": 88, + "amount": -1 + }, + { + "first": 101, + "second": 89, + "amount": -2 + }, + { + "first": 101, + "second": 90, + "amount": 0 + }, + { + "first": 101, + "second": 92, + "amount": -1 + }, + { + "first": 101, + "second": 102, + "amount": 0 + }, + { + "first": 101, + "second": 115, + "amount": 0 + }, + { + "first": 101, + "second": 116, + "amount": 0 + }, + { + "first": 101, + "second": 118, + "amount": 0 + }, + { + "first": 101, + "second": 119, + "amount": 0 + }, + { + "first": 101, + "second": 120, + "amount": -1 + }, + { + "first": 101, + "second": 121, + "amount": 0 + }, + { + "first": 101, + "second": 122, + "amount": 0 + }, + { + "first": 101, + "second": 169, + "amount": 0 + }, + { + "first": 101, + "second": 174, + "amount": 0 + }, + { + "first": 101, + "second": 199, + "amount": 0 + }, + { + "first": 101, + "second": 210, + "amount": 0 + }, + { + "first": 101, + "second": 211, + "amount": 0 + }, + { + "first": 101, + "second": 212, + "amount": 0 + }, + { + "first": 101, + "second": 213, + "amount": 0 + }, + { + "first": 101, + "second": 214, + "amount": 0 + }, + { + "first": 101, + "second": 216, + "amount": 0 + }, + { + "first": 101, + "second": 221, + "amount": -2 + }, + { + "first": 101, + "second": 338, + "amount": 0 + }, + { + "first": 101, + "second": 376, + "amount": -2 + }, + { + "first": 102, + "second": 41, + "amount": 0 + }, + { + "first": 102, + "second": 42, + "amount": 0 + }, + { + "first": 102, + "second": 44, + "amount": 0 + }, + { + "first": 102, + "second": 46, + "amount": 0 + }, + { + "first": 102, + "second": 63, + "amount": 2 + }, + { + "first": 102, + "second": 84, + "amount": 2 + }, + { + "first": 102, + "second": 86, + "amount": 1 + }, + { + "first": 102, + "second": 87, + "amount": 0 + }, + { + "first": 102, + "second": 89, + "amount": 1 + }, + { + "first": 102, + "second": 92, + "amount": 3 + }, + { + "first": 102, + "second": 99, + "amount": 0 + }, + { + "first": 102, + "second": 100, + "amount": 0 + }, + { + "first": 102, + "second": 101, + "amount": 0 + }, + { + "first": 102, + "second": 102, + "amount": 0 + }, + { + "first": 102, + "second": 103, + "amount": 0 + }, + { + "first": 102, + "second": 111, + "amount": 0 + }, + { + "first": 102, + "second": 113, + "amount": 0 + }, + { + "first": 102, + "second": 116, + "amount": 0 + }, + { + "first": 102, + "second": 118, + "amount": 0 + }, + { + "first": 102, + "second": 119, + "amount": 0 + }, + { + "first": 102, + "second": 120, + "amount": 0 + }, + { + "first": 102, + "second": 121, + "amount": 0 + }, + { + "first": 102, + "second": 187, + "amount": 0 + }, + { + "first": 102, + "second": 221, + "amount": 1 + }, + { + "first": 102, + "second": 231, + "amount": 0 + }, + { + "first": 102, + "second": 232, + "amount": 0 + }, + { + "first": 102, + "second": 233, + "amount": 0 + }, + { + "first": 102, + "second": 234, + "amount": 0 + }, + { + "first": 102, + "second": 235, + "amount": 0 + }, + { + "first": 102, + "second": 236, + "amount": 2 + }, + { + "first": 102, + "second": 237, + "amount": 2 + }, + { + "first": 102, + "second": 238, + "amount": 2 + }, + { + "first": 102, + "second": 239, + "amount": 2 + }, + { + "first": 102, + "second": 240, + "amount": 0 + }, + { + "first": 102, + "second": 242, + "amount": 0 + }, + { + "first": 102, + "second": 243, + "amount": 0 + }, + { + "first": 102, + "second": 244, + "amount": 0 + }, + { + "first": 102, + "second": 245, + "amount": 0 + }, + { + "first": 102, + "second": 246, + "amount": 0 + }, + { + "first": 102, + "second": 248, + "amount": 0 + }, + { + "first": 102, + "second": 339, + "amount": 0 + }, + { + "first": 102, + "second": 376, + "amount": 1 + }, + { + "first": 103, + "second": 55, + "amount": -2 + }, + { + "first": 103, + "second": 84, + "amount": -2 + }, + { + "first": 103, + "second": 86, + "amount": -1 + }, + { + "first": 103, + "second": 87, + "amount": -1 + }, + { + "first": 103, + "second": 88, + "amount": 0 + }, + { + "first": 103, + "second": 89, + "amount": -1 + }, + { + "first": 103, + "second": 106, + "amount": 2 + }, + { + "first": 103, + "second": 221, + "amount": -1 + }, + { + "first": 103, + "second": 376, + "amount": -1 + }, + { + "first": 104, + "second": 55, + "amount": -1 + }, + { + "first": 104, + "second": 84, + "amount": -2 + }, + { + "first": 104, + "second": 86, + "amount": -3 + }, + { + "first": 104, + "second": 87, + "amount": -1 + }, + { + "first": 104, + "second": 89, + "amount": -3 + }, + { + "first": 104, + "second": 92, + "amount": -1 + }, + { + "first": 104, + "second": 106, + "amount": 1 + }, + { + "first": 104, + "second": 118, + "amount": 0 + }, + { + "first": 104, + "second": 119, + "amount": 0 + }, + { + "first": 104, + "second": 121, + "amount": 0 + }, + { + "first": 104, + "second": 221, + "amount": -3 + }, + { + "first": 104, + "second": 253, + "amount": 0 + }, + { + "first": 104, + "second": 255, + "amount": 0 + }, + { + "first": 104, + "second": 376, + "amount": -3 + }, + { + "first": 105, + "second": 88, + "amount": 0 + }, + { + "first": 107, + "second": 38, + "amount": -1 + }, + { + "first": 107, + "second": 42, + "amount": -1 + }, + { + "first": 107, + "second": 45, + "amount": -1 + }, + { + "first": 107, + "second": 48, + "amount": 0 + }, + { + "first": 107, + "second": 51, + "amount": 0 + }, + { + "first": 107, + "second": 53, + "amount": 0 + }, + { + "first": 107, + "second": 54, + "amount": 0 + }, + { + "first": 107, + "second": 55, + "amount": -1 + }, + { + "first": 107, + "second": 56, + "amount": 0 + }, + { + "first": 107, + "second": 57, + "amount": 0 + }, + { + "first": 107, + "second": 64, + "amount": -1 + }, + { + "first": 107, + "second": 67, + "amount": -1 + }, + { + "first": 107, + "second": 71, + "amount": -1 + }, + { + "first": 107, + "second": 74, + "amount": 0 + }, + { + "first": 107, + "second": 79, + "amount": -1 + }, + { + "first": 107, + "second": 81, + "amount": -1 + }, + { + "first": 107, + "second": 83, + "amount": -1 + }, + { + "first": 107, + "second": 84, + "amount": -2 + }, + { + "first": 107, + "second": 85, + "amount": 0 + }, + { + "first": 107, + "second": 86, + "amount": -3 + }, + { + "first": 107, + "second": 87, + "amount": -1 + }, + { + "first": 107, + "second": 88, + "amount": 0 + }, + { + "first": 107, + "second": 89, + "amount": -3 + }, + { + "first": 107, + "second": 90, + "amount": 0 + }, + { + "first": 107, + "second": 92, + "amount": -1 + }, + { + "first": 107, + "second": 97, + "amount": -1 + }, + { + "first": 107, + "second": 99, + "amount": 0 + }, + { + "first": 107, + "second": 100, + "amount": 0 + }, + { + "first": 107, + "second": 101, + "amount": 0 + }, + { + "first": 107, + "second": 102, + "amount": 0 + }, + { + "first": 107, + "second": 103, + "amount": 0 + }, + { + "first": 107, + "second": 111, + "amount": 0 + }, + { + "first": 107, + "second": 113, + "amount": 0 + }, + { + "first": 107, + "second": 115, + "amount": 0 + }, + { + "first": 107, + "second": 116, + "amount": 0 + }, + { + "first": 107, + "second": 117, + "amount": -1 + }, + { + "first": 107, + "second": 118, + "amount": 0 + }, + { + "first": 107, + "second": 119, + "amount": -1 + }, + { + "first": 107, + "second": 121, + "amount": 0 + }, + { + "first": 107, + "second": 169, + "amount": -1 + }, + { + "first": 107, + "second": 171, + "amount": -1 + }, + { + "first": 107, + "second": 174, + "amount": -1 + }, + { + "first": 107, + "second": 180, + "amount": -1 + }, + { + "first": 107, + "second": 199, + "amount": -1 + }, + { + "first": 107, + "second": 210, + "amount": -1 + }, + { + "first": 107, + "second": 211, + "amount": -1 + }, + { + "first": 107, + "second": 212, + "amount": -1 + }, + { + "first": 107, + "second": 213, + "amount": -1 + }, + { + "first": 107, + "second": 214, + "amount": -1 + }, + { + "first": 107, + "second": 216, + "amount": -1 + }, + { + "first": 107, + "second": 217, + "amount": 0 + }, + { + "first": 107, + "second": 218, + "amount": 0 + }, + { + "first": 107, + "second": 219, + "amount": 0 + }, + { + "first": 107, + "second": 220, + "amount": 0 + }, + { + "first": 107, + "second": 221, + "amount": -3 + }, + { + "first": 107, + "second": 224, + "amount": -1 + }, + { + "first": 107, + "second": 225, + "amount": -1 + }, + { + "first": 107, + "second": 226, + "amount": -1 + }, + { + "first": 107, + "second": 227, + "amount": -1 + }, + { + "first": 107, + "second": 228, + "amount": -1 + }, + { + "first": 107, + "second": 229, + "amount": -1 + }, + { + "first": 107, + "second": 230, + "amount": -1 + }, + { + "first": 107, + "second": 231, + "amount": 0 + }, + { + "first": 107, + "second": 248, + "amount": 0 + }, + { + "first": 107, + "second": 249, + "amount": 0 + }, + { + "first": 107, + "second": 250, + "amount": -1 + }, + { + "first": 107, + "second": 251, + "amount": 0 + }, + { + "first": 107, + "second": 252, + "amount": 0 + }, + { + "first": 107, + "second": 338, + "amount": -1 + }, + { + "first": 107, + "second": 339, + "amount": 0 + }, + { + "first": 107, + "second": 376, + "amount": -3 + }, + { + "first": 108, + "second": 88, + "amount": 0 + }, + { + "first": 109, + "second": 55, + "amount": -1 + }, + { + "first": 109, + "second": 84, + "amount": -2 + }, + { + "first": 109, + "second": 86, + "amount": -3 + }, + { + "first": 109, + "second": 87, + "amount": -1 + }, + { + "first": 109, + "second": 89, + "amount": -3 + }, + { + "first": 109, + "second": 92, + "amount": -1 + }, + { + "first": 109, + "second": 106, + "amount": 1 + }, + { + "first": 109, + "second": 118, + "amount": 0 + }, + { + "first": 109, + "second": 119, + "amount": 0 + }, + { + "first": 109, + "second": 121, + "amount": 0 + }, + { + "first": 109, + "second": 221, + "amount": -3 + }, + { + "first": 109, + "second": 253, + "amount": 0 + }, + { + "first": 109, + "second": 255, + "amount": 0 + }, + { + "first": 109, + "second": 376, + "amount": -3 + }, + { + "first": 110, + "second": 55, + "amount": -1 + }, + { + "first": 110, + "second": 84, + "amount": -2 + }, + { + "first": 110, + "second": 86, + "amount": -3 + }, + { + "first": 110, + "second": 87, + "amount": -1 + }, + { + "first": 110, + "second": 89, + "amount": -3 + }, + { + "first": 110, + "second": 92, + "amount": -1 + }, + { + "first": 110, + "second": 106, + "amount": 1 + }, + { + "first": 110, + "second": 118, + "amount": 0 + }, + { + "first": 110, + "second": 119, + "amount": 0 + }, + { + "first": 110, + "second": 121, + "amount": 0 + }, + { + "first": 110, + "second": 221, + "amount": -3 + }, + { + "first": 110, + "second": 253, + "amount": 0 + }, + { + "first": 110, + "second": 255, + "amount": 0 + }, + { + "first": 110, + "second": 376, + "amount": -3 + }, + { + "first": 111, + "second": 42, + "amount": -2 + }, + { + "first": 111, + "second": 44, + "amount": -1 + }, + { + "first": 111, + "second": 46, + "amount": -1 + }, + { + "first": 111, + "second": 55, + "amount": -2 + }, + { + "first": 111, + "second": 64, + "amount": 0 + }, + { + "first": 111, + "second": 65, + "amount": 0 + }, + { + "first": 111, + "second": 67, + "amount": 0 + }, + { + "first": 111, + "second": 71, + "amount": 0 + }, + { + "first": 111, + "second": 79, + "amount": 0 + }, + { + "first": 111, + "second": 81, + "amount": 0 + }, + { + "first": 111, + "second": 83, + "amount": 0 + }, + { + "first": 111, + "second": 84, + "amount": -4 + }, + { + "first": 111, + "second": 86, + "amount": -4 + }, + { + "first": 111, + "second": 87, + "amount": -1 + }, + { + "first": 111, + "second": 88, + "amount": -2 + }, + { + "first": 111, + "second": 89, + "amount": -4 + }, + { + "first": 111, + "second": 90, + "amount": 0 + }, + { + "first": 111, + "second": 102, + "amount": 0 + }, + { + "first": 111, + "second": 115, + "amount": 0 + }, + { + "first": 111, + "second": 116, + "amount": 0 + }, + { + "first": 111, + "second": 118, + "amount": 0 + }, + { + "first": 111, + "second": 119, + "amount": 0 + }, + { + "first": 111, + "second": 120, + "amount": -1 + }, + { + "first": 111, + "second": 121, + "amount": 0 + }, + { + "first": 111, + "second": 122, + "amount": 0 + }, + { + "first": 111, + "second": 169, + "amount": 0 + }, + { + "first": 111, + "second": 174, + "amount": 0 + }, + { + "first": 111, + "second": 192, + "amount": 0 + }, + { + "first": 111, + "second": 193, + "amount": 0 + }, + { + "first": 111, + "second": 194, + "amount": 0 + }, + { + "first": 111, + "second": 195, + "amount": 0 + }, + { + "first": 111, + "second": 196, + "amount": 0 + }, + { + "first": 111, + "second": 197, + "amount": 0 + }, + { + "first": 111, + "second": 199, + "amount": 0 + }, + { + "first": 111, + "second": 210, + "amount": 0 + }, + { + "first": 111, + "second": 211, + "amount": 0 + }, + { + "first": 111, + "second": 212, + "amount": 0 + }, + { + "first": 111, + "second": 213, + "amount": 0 + }, + { + "first": 111, + "second": 214, + "amount": 0 + }, + { + "first": 111, + "second": 216, + "amount": 0 + }, + { + "first": 111, + "second": 221, + "amount": -4 + }, + { + "first": 111, + "second": 338, + "amount": 0 + }, + { + "first": 111, + "second": 376, + "amount": -4 + }, + { + "first": 112, + "second": 42, + "amount": -2 + }, + { + "first": 112, + "second": 44, + "amount": -1 + }, + { + "first": 112, + "second": 46, + "amount": -1 + }, + { + "first": 112, + "second": 55, + "amount": -2 + }, + { + "first": 112, + "second": 64, + "amount": 0 + }, + { + "first": 112, + "second": 65, + "amount": 0 + }, + { + "first": 112, + "second": 67, + "amount": 0 + }, + { + "first": 112, + "second": 71, + "amount": 0 + }, + { + "first": 112, + "second": 79, + "amount": 0 + }, + { + "first": 112, + "second": 81, + "amount": 0 + }, + { + "first": 112, + "second": 83, + "amount": 0 + }, + { + "first": 112, + "second": 84, + "amount": -4 + }, + { + "first": 112, + "second": 86, + "amount": -4 + }, + { + "first": 112, + "second": 87, + "amount": -1 + }, + { + "first": 112, + "second": 88, + "amount": -2 + }, + { + "first": 112, + "second": 89, + "amount": -4 + }, + { + "first": 112, + "second": 90, + "amount": 0 + }, + { + "first": 112, + "second": 102, + "amount": 0 + }, + { + "first": 112, + "second": 115, + "amount": 0 + }, + { + "first": 112, + "second": 116, + "amount": 0 + }, + { + "first": 112, + "second": 118, + "amount": 0 + }, + { + "first": 112, + "second": 119, + "amount": 0 + }, + { + "first": 112, + "second": 120, + "amount": -1 + }, + { + "first": 112, + "second": 121, + "amount": 0 + }, + { + "first": 112, + "second": 122, + "amount": 0 + }, + { + "first": 112, + "second": 169, + "amount": 0 + }, + { + "first": 112, + "second": 174, + "amount": 0 + }, + { + "first": 112, + "second": 192, + "amount": 0 + }, + { + "first": 112, + "second": 193, + "amount": 0 + }, + { + "first": 112, + "second": 194, + "amount": 0 + }, + { + "first": 112, + "second": 195, + "amount": 0 + }, + { + "first": 112, + "second": 196, + "amount": 0 + }, + { + "first": 112, + "second": 197, + "amount": 0 + }, + { + "first": 112, + "second": 199, + "amount": 0 + }, + { + "first": 112, + "second": 210, + "amount": 0 + }, + { + "first": 112, + "second": 211, + "amount": 0 + }, + { + "first": 112, + "second": 212, + "amount": 0 + }, + { + "first": 112, + "second": 213, + "amount": 0 + }, + { + "first": 112, + "second": 214, + "amount": 0 + }, + { + "first": 112, + "second": 216, + "amount": 0 + }, + { + "first": 112, + "second": 221, + "amount": -4 + }, + { + "first": 112, + "second": 338, + "amount": 0 + }, + { + "first": 112, + "second": 376, + "amount": -4 + }, + { + "first": 113, + "second": 55, + "amount": -2 + }, + { + "first": 113, + "second": 84, + "amount": -2 + }, + { + "first": 113, + "second": 86, + "amount": -1 + }, + { + "first": 113, + "second": 87, + "amount": -1 + }, + { + "first": 113, + "second": 88, + "amount": 0 + }, + { + "first": 113, + "second": 89, + "amount": -1 + }, + { + "first": 113, + "second": 106, + "amount": 2 + }, + { + "first": 113, + "second": 221, + "amount": -1 + }, + { + "first": 113, + "second": 376, + "amount": -1 + }, + { + "first": 114, + "second": 38, + "amount": -2 + }, + { + "first": 114, + "second": 45, + "amount": 0 + }, + { + "first": 114, + "second": 50, + "amount": -1 + }, + { + "first": 114, + "second": 51, + "amount": -1 + }, + { + "first": 114, + "second": 55, + "amount": -1 + }, + { + "first": 114, + "second": 56, + "amount": 0 + }, + { + "first": 114, + "second": 64, + "amount": -1 + }, + { + "first": 114, + "second": 65, + "amount": -2 + }, + { + "first": 114, + "second": 67, + "amount": -1 + }, + { + "first": 114, + "second": 71, + "amount": -1 + }, + { + "first": 114, + "second": 74, + "amount": -4 + }, + { + "first": 114, + "second": 79, + "amount": -1 + }, + { + "first": 114, + "second": 81, + "amount": -1 + }, + { + "first": 114, + "second": 83, + "amount": 0 + }, + { + "first": 114, + "second": 84, + "amount": -1 + }, + { + "first": 114, + "second": 86, + "amount": -1 + }, + { + "first": 114, + "second": 87, + "amount": 0 + }, + { + "first": 114, + "second": 88, + "amount": -2 + }, + { + "first": 114, + "second": 89, + "amount": -1 + }, + { + "first": 114, + "second": 90, + "amount": -1 + }, + { + "first": 114, + "second": 97, + "amount": -1 + }, + { + "first": 114, + "second": 99, + "amount": 0 + }, + { + "first": 114, + "second": 100, + "amount": 0 + }, + { + "first": 114, + "second": 101, + "amount": 0 + }, + { + "first": 114, + "second": 102, + "amount": 0 + }, + { + "first": 114, + "second": 103, + "amount": 0 + }, + { + "first": 114, + "second": 109, + "amount": 0 + }, + { + "first": 114, + "second": 110, + "amount": 0 + }, + { + "first": 114, + "second": 111, + "amount": 0 + }, + { + "first": 114, + "second": 112, + "amount": 0 + }, + { + "first": 114, + "second": 113, + "amount": 0 + }, + { + "first": 114, + "second": 114, + "amount": 0 + }, + { + "first": 114, + "second": 115, + "amount": 0 + }, + { + "first": 114, + "second": 116, + "amount": 1 + }, + { + "first": 114, + "second": 117, + "amount": 0 + }, + { + "first": 114, + "second": 118, + "amount": 0 + }, + { + "first": 114, + "second": 119, + "amount": 0 + }, + { + "first": 114, + "second": 120, + "amount": 0 + }, + { + "first": 114, + "second": 121, + "amount": 0 + }, + { + "first": 114, + "second": 169, + "amount": -1 + }, + { + "first": 114, + "second": 171, + "amount": -1 + }, + { + "first": 114, + "second": 174, + "amount": -1 + }, + { + "first": 114, + "second": 180, + "amount": -1 + }, + { + "first": 114, + "second": 181, + "amount": 0 + }, + { + "first": 114, + "second": 187, + "amount": 0 + }, + { + "first": 114, + "second": 192, + "amount": -2 + }, + { + "first": 114, + "second": 193, + "amount": -2 + }, + { + "first": 114, + "second": 194, + "amount": -2 + }, + { + "first": 114, + "second": 195, + "amount": -2 + }, + { + "first": 114, + "second": 196, + "amount": -2 + }, + { + "first": 114, + "second": 197, + "amount": -2 + }, + { + "first": 114, + "second": 199, + "amount": -1 + }, + { + "first": 114, + "second": 210, + "amount": -1 + }, + { + "first": 114, + "second": 211, + "amount": -1 + }, + { + "first": 114, + "second": 212, + "amount": -1 + }, + { + "first": 114, + "second": 213, + "amount": -1 + }, + { + "first": 114, + "second": 214, + "amount": -1 + }, + { + "first": 114, + "second": 216, + "amount": -1 + }, + { + "first": 114, + "second": 221, + "amount": -1 + }, + { + "first": 114, + "second": 224, + "amount": -1 + }, + { + "first": 114, + "second": 225, + "amount": -1 + }, + { + "first": 114, + "second": 226, + "amount": -1 + }, + { + "first": 114, + "second": 227, + "amount": -1 + }, + { + "first": 114, + "second": 228, + "amount": -1 + }, + { + "first": 114, + "second": 229, + "amount": -1 + }, + { + "first": 114, + "second": 230, + "amount": -1 + }, + { + "first": 114, + "second": 231, + "amount": 0 + }, + { + "first": 114, + "second": 248, + "amount": 0 + }, + { + "first": 114, + "second": 250, + "amount": 0 + }, + { + "first": 114, + "second": 338, + "amount": -1 + }, + { + "first": 114, + "second": 339, + "amount": 0 + }, + { + "first": 114, + "second": 376, + "amount": -1 + }, + { + "first": 115, + "second": 42, + "amount": -1 + }, + { + "first": 115, + "second": 48, + "amount": 0 + }, + { + "first": 115, + "second": 52, + "amount": 0 + }, + { + "first": 115, + "second": 55, + "amount": -2 + }, + { + "first": 115, + "second": 57, + "amount": -1 + }, + { + "first": 115, + "second": 64, + "amount": 0 + }, + { + "first": 115, + "second": 65, + "amount": 0 + }, + { + "first": 115, + "second": 67, + "amount": 0 + }, + { + "first": 115, + "second": 71, + "amount": 0 + }, + { + "first": 115, + "second": 79, + "amount": 0 + }, + { + "first": 115, + "second": 81, + "amount": 0 + }, + { + "first": 115, + "second": 84, + "amount": -3 + }, + { + "first": 115, + "second": 85, + "amount": 0 + }, + { + "first": 115, + "second": 86, + "amount": -4 + }, + { + "first": 115, + "second": 88, + "amount": -1 + }, + { + "first": 115, + "second": 89, + "amount": -4 + }, + { + "first": 115, + "second": 90, + "amount": 0 + }, + { + "first": 115, + "second": 97, + "amount": 0 + }, + { + "first": 115, + "second": 99, + "amount": 0 + }, + { + "first": 115, + "second": 100, + "amount": 0 + }, + { + "first": 115, + "second": 101, + "amount": 0 + }, + { + "first": 115, + "second": 102, + "amount": 0 + }, + { + "first": 115, + "second": 103, + "amount": 0 + }, + { + "first": 115, + "second": 111, + "amount": 0 + }, + { + "first": 115, + "second": 113, + "amount": 0 + }, + { + "first": 115, + "second": 116, + "amount": 0 + }, + { + "first": 115, + "second": 118, + "amount": 0 + }, + { + "first": 115, + "second": 119, + "amount": 0 + }, + { + "first": 115, + "second": 120, + "amount": 0 + }, + { + "first": 115, + "second": 121, + "amount": 0 + }, + { + "first": 115, + "second": 122, + "amount": 0 + }, + { + "first": 115, + "second": 169, + "amount": 0 + }, + { + "first": 115, + "second": 174, + "amount": 0 + }, + { + "first": 115, + "second": 180, + "amount": 0 + }, + { + "first": 115, + "second": 192, + "amount": 0 + }, + { + "first": 115, + "second": 193, + "amount": 0 + }, + { + "first": 115, + "second": 194, + "amount": 0 + }, + { + "first": 115, + "second": 195, + "amount": 0 + }, + { + "first": 115, + "second": 196, + "amount": 0 + }, + { + "first": 115, + "second": 197, + "amount": 0 + }, + { + "first": 115, + "second": 199, + "amount": 0 + }, + { + "first": 115, + "second": 210, + "amount": 0 + }, + { + "first": 115, + "second": 211, + "amount": 0 + }, + { + "first": 115, + "second": 212, + "amount": 0 + }, + { + "first": 115, + "second": 213, + "amount": 0 + }, + { + "first": 115, + "second": 214, + "amount": 0 + }, + { + "first": 115, + "second": 216, + "amount": 0 + }, + { + "first": 115, + "second": 217, + "amount": 0 + }, + { + "first": 115, + "second": 218, + "amount": 0 + }, + { + "first": 115, + "second": 219, + "amount": 0 + }, + { + "first": 115, + "second": 220, + "amount": 0 + }, + { + "first": 115, + "second": 221, + "amount": -4 + }, + { + "first": 115, + "second": 224, + "amount": 0 + }, + { + "first": 115, + "second": 225, + "amount": 0 + }, + { + "first": 115, + "second": 226, + "amount": 0 + }, + { + "first": 115, + "second": 227, + "amount": 0 + }, + { + "first": 115, + "second": 228, + "amount": 0 + }, + { + "first": 115, + "second": 229, + "amount": 0 + }, + { + "first": 115, + "second": 230, + "amount": 0 + }, + { + "first": 115, + "second": 231, + "amount": 0 + }, + { + "first": 115, + "second": 248, + "amount": 0 + }, + { + "first": 115, + "second": 338, + "amount": 0 + }, + { + "first": 115, + "second": 339, + "amount": 0 + }, + { + "first": 115, + "second": 376, + "amount": -4 + }, + { + "first": 116, + "second": 45, + "amount": 0 + }, + { + "first": 116, + "second": 87, + "amount": 0 + }, + { + "first": 116, + "second": 88, + "amount": 0 + }, + { + "first": 116, + "second": 97, + "amount": 0 + }, + { + "first": 116, + "second": 99, + "amount": 0 + }, + { + "first": 116, + "second": 100, + "amount": 0 + }, + { + "first": 116, + "second": 101, + "amount": 0 + }, + { + "first": 116, + "second": 102, + "amount": 0 + }, + { + "first": 116, + "second": 103, + "amount": 0 + }, + { + "first": 116, + "second": 111, + "amount": 0 + }, + { + "first": 116, + "second": 113, + "amount": 0 + }, + { + "first": 116, + "second": 115, + "amount": 0 + }, + { + "first": 116, + "second": 116, + "amount": -1 + }, + { + "first": 116, + "second": 117, + "amount": -1 + }, + { + "first": 116, + "second": 118, + "amount": 0 + }, + { + "first": 116, + "second": 119, + "amount": 0 + }, + { + "first": 116, + "second": 120, + "amount": -1 + }, + { + "first": 116, + "second": 121, + "amount": 0 + }, + { + "first": 116, + "second": 122, + "amount": 0 + }, + { + "first": 116, + "second": 171, + "amount": 0 + }, + { + "first": 116, + "second": 180, + "amount": 0 + }, + { + "first": 116, + "second": 224, + "amount": 0 + }, + { + "first": 116, + "second": 225, + "amount": 0 + }, + { + "first": 116, + "second": 226, + "amount": 0 + }, + { + "first": 116, + "second": 227, + "amount": 0 + }, + { + "first": 116, + "second": 228, + "amount": 0 + }, + { + "first": 116, + "second": 229, + "amount": 0 + }, + { + "first": 116, + "second": 230, + "amount": 0 + }, + { + "first": 116, + "second": 231, + "amount": 0 + }, + { + "first": 116, + "second": 232, + "amount": 0 + }, + { + "first": 116, + "second": 233, + "amount": 0 + }, + { + "first": 116, + "second": 234, + "amount": 0 + }, + { + "first": 116, + "second": 235, + "amount": 0 + }, + { + "first": 116, + "second": 240, + "amount": 0 + }, + { + "first": 116, + "second": 241, + "amount": -1 + }, + { + "first": 116, + "second": 242, + "amount": 0 + }, + { + "first": 116, + "second": 243, + "amount": 0 + }, + { + "first": 116, + "second": 244, + "amount": 0 + }, + { + "first": 116, + "second": 245, + "amount": 0 + }, + { + "first": 116, + "second": 246, + "amount": 0 + }, + { + "first": 116, + "second": 248, + "amount": 0 + }, + { + "first": 116, + "second": 250, + "amount": -1 + }, + { + "first": 116, + "second": 339, + "amount": 0 + }, + { + "first": 117, + "second": 55, + "amount": -2 + }, + { + "first": 117, + "second": 84, + "amount": -2 + }, + { + "first": 117, + "second": 86, + "amount": -1 + }, + { + "first": 117, + "second": 87, + "amount": -1 + }, + { + "first": 117, + "second": 88, + "amount": 0 + }, + { + "first": 117, + "second": 89, + "amount": -1 + }, + { + "first": 117, + "second": 106, + "amount": 2 + }, + { + "first": 117, + "second": 221, + "amount": -1 + }, + { + "first": 117, + "second": 376, + "amount": -1 + }, + { + "first": 118, + "second": 64, + "amount": -1 + }, + { + "first": 118, + "second": 65, + "amount": -2 + }, + { + "first": 118, + "second": 67, + "amount": -1 + }, + { + "first": 118, + "second": 71, + "amount": -1 + }, + { + "first": 118, + "second": 74, + "amount": -1 + }, + { + "first": 118, + "second": 79, + "amount": -1 + }, + { + "first": 118, + "second": 81, + "amount": -1 + }, + { + "first": 118, + "second": 83, + "amount": 0 + }, + { + "first": 118, + "second": 84, + "amount": -2 + }, + { + "first": 118, + "second": 86, + "amount": -2 + }, + { + "first": 118, + "second": 87, + "amount": 0 + }, + { + "first": 118, + "second": 88, + "amount": -1 + }, + { + "first": 118, + "second": 89, + "amount": -2 + }, + { + "first": 118, + "second": 90, + "amount": 0 + }, + { + "first": 118, + "second": 97, + "amount": 0 + }, + { + "first": 118, + "second": 99, + "amount": 0 + }, + { + "first": 118, + "second": 100, + "amount": 0 + }, + { + "first": 118, + "second": 101, + "amount": 0 + }, + { + "first": 118, + "second": 103, + "amount": 0 + }, + { + "first": 118, + "second": 111, + "amount": 0 + }, + { + "first": 118, + "second": 113, + "amount": 0 + }, + { + "first": 118, + "second": 115, + "amount": 0 + }, + { + "first": 118, + "second": 122, + "amount": 0 + }, + { + "first": 118, + "second": 169, + "amount": -1 + }, + { + "first": 118, + "second": 174, + "amount": -1 + }, + { + "first": 118, + "second": 180, + "amount": 0 + }, + { + "first": 118, + "second": 192, + "amount": -2 + }, + { + "first": 118, + "second": 193, + "amount": -2 + }, + { + "first": 118, + "second": 194, + "amount": -2 + }, + { + "first": 118, + "second": 195, + "amount": -2 + }, + { + "first": 118, + "second": 196, + "amount": -2 + }, + { + "first": 118, + "second": 197, + "amount": -2 + }, + { + "first": 118, + "second": 199, + "amount": -1 + }, + { + "first": 118, + "second": 210, + "amount": -1 + }, + { + "first": 118, + "second": 211, + "amount": -1 + }, + { + "first": 118, + "second": 212, + "amount": -1 + }, + { + "first": 118, + "second": 213, + "amount": -1 + }, + { + "first": 118, + "second": 214, + "amount": -1 + }, + { + "first": 118, + "second": 216, + "amount": -1 + }, + { + "first": 118, + "second": 221, + "amount": -2 + }, + { + "first": 118, + "second": 224, + "amount": 0 + }, + { + "first": 118, + "second": 225, + "amount": 0 + }, + { + "first": 118, + "second": 226, + "amount": 0 + }, + { + "first": 118, + "second": 227, + "amount": 0 + }, + { + "first": 118, + "second": 228, + "amount": 0 + }, + { + "first": 118, + "second": 229, + "amount": 0 + }, + { + "first": 118, + "second": 230, + "amount": 0 + }, + { + "first": 118, + "second": 231, + "amount": 0 + }, + { + "first": 118, + "second": 248, + "amount": 0 + }, + { + "first": 118, + "second": 338, + "amount": -1 + }, + { + "first": 118, + "second": 339, + "amount": 0 + }, + { + "first": 118, + "second": 376, + "amount": -2 + }, + { + "first": 119, + "second": 38, + "amount": 0 + }, + { + "first": 119, + "second": 44, + "amount": -1 + }, + { + "first": 119, + "second": 46, + "amount": -1 + }, + { + "first": 119, + "second": 48, + "amount": 0 + }, + { + "first": 119, + "second": 50, + "amount": 0 + }, + { + "first": 119, + "second": 51, + "amount": 0 + }, + { + "first": 119, + "second": 52, + "amount": 0 + }, + { + "first": 119, + "second": 53, + "amount": 0 + }, + { + "first": 119, + "second": 54, + "amount": 0 + }, + { + "first": 119, + "second": 55, + "amount": -1 + }, + { + "first": 119, + "second": 56, + "amount": 0 + }, + { + "first": 119, + "second": 64, + "amount": -1 + }, + { + "first": 119, + "second": 65, + "amount": -2 + }, + { + "first": 119, + "second": 67, + "amount": -1 + }, + { + "first": 119, + "second": 71, + "amount": -1 + }, + { + "first": 119, + "second": 74, + "amount": -1 + }, + { + "first": 119, + "second": 79, + "amount": -1 + }, + { + "first": 119, + "second": 81, + "amount": -1 + }, + { + "first": 119, + "second": 83, + "amount": 0 + }, + { + "first": 119, + "second": 84, + "amount": -2 + }, + { + "first": 119, + "second": 86, + "amount": -1 + }, + { + "first": 119, + "second": 87, + "amount": -1 + }, + { + "first": 119, + "second": 88, + "amount": -2 + }, + { + "first": 119, + "second": 89, + "amount": -1 + }, + { + "first": 119, + "second": 90, + "amount": -1 + }, + { + "first": 119, + "second": 97, + "amount": 0 + }, + { + "first": 119, + "second": 99, + "amount": 0 + }, + { + "first": 119, + "second": 100, + "amount": 0 + }, + { + "first": 119, + "second": 101, + "amount": 0 + }, + { + "first": 119, + "second": 103, + "amount": 0 + }, + { + "first": 119, + "second": 111, + "amount": 0 + }, + { + "first": 119, + "second": 113, + "amount": 0 + }, + { + "first": 119, + "second": 115, + "amount": 0 + }, + { + "first": 119, + "second": 120, + "amount": 0 + }, + { + "first": 119, + "second": 122, + "amount": 0 + }, + { + "first": 119, + "second": 169, + "amount": -1 + }, + { + "first": 119, + "second": 174, + "amount": -1 + }, + { + "first": 119, + "second": 180, + "amount": 0 + }, + { + "first": 119, + "second": 192, + "amount": -2 + }, + { + "first": 119, + "second": 193, + "amount": -2 + }, + { + "first": 119, + "second": 194, + "amount": -2 + }, + { + "first": 119, + "second": 195, + "amount": -2 + }, + { + "first": 119, + "second": 196, + "amount": -2 + }, + { + "first": 119, + "second": 197, + "amount": -2 + }, + { + "first": 119, + "second": 199, + "amount": -1 + }, + { + "first": 119, + "second": 210, + "amount": -1 + }, + { + "first": 119, + "second": 211, + "amount": -1 + }, + { + "first": 119, + "second": 212, + "amount": -1 + }, + { + "first": 119, + "second": 213, + "amount": -1 + }, + { + "first": 119, + "second": 214, + "amount": -1 + }, + { + "first": 119, + "second": 216, + "amount": -1 + }, + { + "first": 119, + "second": 221, + "amount": -1 + }, + { + "first": 119, + "second": 224, + "amount": 0 + }, + { + "first": 119, + "second": 225, + "amount": 0 + }, + { + "first": 119, + "second": 226, + "amount": 0 + }, + { + "first": 119, + "second": 227, + "amount": 0 + }, + { + "first": 119, + "second": 228, + "amount": 0 + }, + { + "first": 119, + "second": 229, + "amount": 0 + }, + { + "first": 119, + "second": 230, + "amount": 0 + }, + { + "first": 119, + "second": 231, + "amount": 0 + }, + { + "first": 119, + "second": 232, + "amount": 0 + }, + { + "first": 119, + "second": 233, + "amount": 0 + }, + { + "first": 119, + "second": 234, + "amount": 0 + }, + { + "first": 119, + "second": 235, + "amount": 0 + }, + { + "first": 119, + "second": 240, + "amount": 0 + }, + { + "first": 119, + "second": 242, + "amount": 0 + }, + { + "first": 119, + "second": 243, + "amount": 0 + }, + { + "first": 119, + "second": 244, + "amount": 0 + }, + { + "first": 119, + "second": 245, + "amount": 0 + }, + { + "first": 119, + "second": 246, + "amount": 0 + }, + { + "first": 119, + "second": 248, + "amount": 0 + }, + { + "first": 119, + "second": 338, + "amount": -1 + }, + { + "first": 119, + "second": 339, + "amount": 0 + }, + { + "first": 119, + "second": 376, + "amount": -1 + }, + { + "first": 120, + "second": 38, + "amount": -1 + }, + { + "first": 120, + "second": 42, + "amount": -1 + }, + { + "first": 120, + "second": 45, + "amount": -1 + }, + { + "first": 120, + "second": 48, + "amount": 0 + }, + { + "first": 120, + "second": 51, + "amount": 0 + }, + { + "first": 120, + "second": 53, + "amount": 0 + }, + { + "first": 120, + "second": 54, + "amount": 0 + }, + { + "first": 120, + "second": 55, + "amount": -1 + }, + { + "first": 120, + "second": 56, + "amount": 0 + }, + { + "first": 120, + "second": 57, + "amount": 0 + }, + { + "first": 120, + "second": 64, + "amount": -1 + }, + { + "first": 120, + "second": 67, + "amount": -1 + }, + { + "first": 120, + "second": 71, + "amount": -1 + }, + { + "first": 120, + "second": 74, + "amount": 0 + }, + { + "first": 120, + "second": 79, + "amount": -1 + }, + { + "first": 120, + "second": 81, + "amount": -1 + }, + { + "first": 120, + "second": 83, + "amount": -1 + }, + { + "first": 120, + "second": 84, + "amount": -2 + }, + { + "first": 120, + "second": 85, + "amount": 0 + }, + { + "first": 120, + "second": 86, + "amount": -3 + }, + { + "first": 120, + "second": 87, + "amount": -1 + }, + { + "first": 120, + "second": 88, + "amount": 0 + }, + { + "first": 120, + "second": 89, + "amount": -3 + }, + { + "first": 120, + "second": 90, + "amount": 0 + }, + { + "first": 120, + "second": 92, + "amount": -1 + }, + { + "first": 120, + "second": 97, + "amount": -1 + }, + { + "first": 120, + "second": 99, + "amount": 0 + }, + { + "first": 120, + "second": 100, + "amount": 0 + }, + { + "first": 120, + "second": 101, + "amount": 0 + }, + { + "first": 120, + "second": 102, + "amount": 0 + }, + { + "first": 120, + "second": 103, + "amount": 0 + }, + { + "first": 120, + "second": 111, + "amount": 0 + }, + { + "first": 120, + "second": 113, + "amount": 0 + }, + { + "first": 120, + "second": 115, + "amount": 0 + }, + { + "first": 120, + "second": 116, + "amount": 0 + }, + { + "first": 120, + "second": 117, + "amount": -1 + }, + { + "first": 120, + "second": 118, + "amount": 0 + }, + { + "first": 120, + "second": 119, + "amount": -1 + }, + { + "first": 120, + "second": 121, + "amount": 0 + }, + { + "first": 120, + "second": 169, + "amount": -1 + }, + { + "first": 120, + "second": 171, + "amount": -1 + }, + { + "first": 120, + "second": 174, + "amount": -1 + }, + { + "first": 120, + "second": 180, + "amount": -1 + }, + { + "first": 120, + "second": 199, + "amount": -1 + }, + { + "first": 120, + "second": 210, + "amount": -1 + }, + { + "first": 120, + "second": 211, + "amount": -1 + }, + { + "first": 120, + "second": 212, + "amount": -1 + }, + { + "first": 120, + "second": 213, + "amount": -1 + }, + { + "first": 120, + "second": 214, + "amount": -1 + }, + { + "first": 120, + "second": 216, + "amount": -1 + }, + { + "first": 120, + "second": 217, + "amount": 0 + }, + { + "first": 120, + "second": 218, + "amount": 0 + }, + { + "first": 120, + "second": 219, + "amount": 0 + }, + { + "first": 120, + "second": 220, + "amount": 0 + }, + { + "first": 120, + "second": 221, + "amount": -3 + }, + { + "first": 120, + "second": 224, + "amount": -1 + }, + { + "first": 120, + "second": 225, + "amount": -1 + }, + { + "first": 120, + "second": 226, + "amount": -1 + }, + { + "first": 120, + "second": 227, + "amount": -1 + }, + { + "first": 120, + "second": 228, + "amount": -1 + }, + { + "first": 120, + "second": 229, + "amount": -1 + }, + { + "first": 120, + "second": 230, + "amount": -1 + }, + { + "first": 120, + "second": 231, + "amount": 0 + }, + { + "first": 120, + "second": 248, + "amount": 0 + }, + { + "first": 120, + "second": 249, + "amount": 0 + }, + { + "first": 120, + "second": 250, + "amount": -1 + }, + { + "first": 120, + "second": 251, + "amount": 0 + }, + { + "first": 120, + "second": 252, + "amount": 0 + }, + { + "first": 120, + "second": 338, + "amount": -1 + }, + { + "first": 120, + "second": 339, + "amount": 0 + }, + { + "first": 120, + "second": 376, + "amount": -3 + }, + { + "first": 121, + "second": 64, + "amount": -1 + }, + { + "first": 121, + "second": 65, + "amount": -2 + }, + { + "first": 121, + "second": 67, + "amount": -1 + }, + { + "first": 121, + "second": 71, + "amount": -1 + }, + { + "first": 121, + "second": 74, + "amount": -1 + }, + { + "first": 121, + "second": 79, + "amount": -1 + }, + { + "first": 121, + "second": 81, + "amount": -1 + }, + { + "first": 121, + "second": 83, + "amount": 0 + }, + { + "first": 121, + "second": 84, + "amount": -2 + }, + { + "first": 121, + "second": 86, + "amount": -2 + }, + { + "first": 121, + "second": 87, + "amount": 0 + }, + { + "first": 121, + "second": 88, + "amount": -1 + }, + { + "first": 121, + "second": 89, + "amount": -2 + }, + { + "first": 121, + "second": 90, + "amount": 0 + }, + { + "first": 121, + "second": 97, + "amount": 0 + }, + { + "first": 121, + "second": 99, + "amount": 0 + }, + { + "first": 121, + "second": 100, + "amount": 0 + }, + { + "first": 121, + "second": 101, + "amount": 0 + }, + { + "first": 121, + "second": 103, + "amount": 0 + }, + { + "first": 121, + "second": 111, + "amount": 0 + }, + { + "first": 121, + "second": 113, + "amount": 0 + }, + { + "first": 121, + "second": 115, + "amount": 0 + }, + { + "first": 121, + "second": 122, + "amount": 0 + }, + { + "first": 121, + "second": 169, + "amount": -1 + }, + { + "first": 121, + "second": 174, + "amount": -1 + }, + { + "first": 121, + "second": 180, + "amount": 0 + }, + { + "first": 121, + "second": 192, + "amount": -2 + }, + { + "first": 121, + "second": 193, + "amount": -2 + }, + { + "first": 121, + "second": 194, + "amount": -2 + }, + { + "first": 121, + "second": 195, + "amount": -2 + }, + { + "first": 121, + "second": 196, + "amount": -2 + }, + { + "first": 121, + "second": 197, + "amount": -2 + }, + { + "first": 121, + "second": 199, + "amount": -1 + }, + { + "first": 121, + "second": 210, + "amount": -1 + }, + { + "first": 121, + "second": 211, + "amount": -1 + }, + { + "first": 121, + "second": 212, + "amount": -1 + }, + { + "first": 121, + "second": 213, + "amount": -1 + }, + { + "first": 121, + "second": 214, + "amount": -1 + }, + { + "first": 121, + "second": 216, + "amount": -1 + }, + { + "first": 121, + "second": 221, + "amount": -2 + }, + { + "first": 121, + "second": 224, + "amount": 0 + }, + { + "first": 121, + "second": 225, + "amount": 0 + }, + { + "first": 121, + "second": 226, + "amount": 0 + }, + { + "first": 121, + "second": 227, + "amount": 0 + }, + { + "first": 121, + "second": 228, + "amount": 0 + }, + { + "first": 121, + "second": 229, + "amount": 0 + }, + { + "first": 121, + "second": 230, + "amount": 0 + }, + { + "first": 121, + "second": 231, + "amount": 0 + }, + { + "first": 121, + "second": 248, + "amount": 0 + }, + { + "first": 121, + "second": 338, + "amount": -1 + }, + { + "first": 121, + "second": 339, + "amount": 0 + }, + { + "first": 121, + "second": 376, + "amount": -2 + }, + { + "first": 122, + "second": 64, + "amount": 0 + }, + { + "first": 122, + "second": 67, + "amount": 0 + }, + { + "first": 122, + "second": 71, + "amount": 0 + }, + { + "first": 122, + "second": 79, + "amount": 0 + }, + { + "first": 122, + "second": 81, + "amount": 0 + }, + { + "first": 122, + "second": 84, + "amount": -2 + }, + { + "first": 122, + "second": 86, + "amount": -2 + }, + { + "first": 122, + "second": 87, + "amount": 0 + }, + { + "first": 122, + "second": 88, + "amount": 0 + }, + { + "first": 122, + "second": 89, + "amount": -2 + }, + { + "first": 122, + "second": 99, + "amount": 0 + }, + { + "first": 122, + "second": 100, + "amount": 0 + }, + { + "first": 122, + "second": 101, + "amount": 0 + }, + { + "first": 122, + "second": 103, + "amount": 0 + }, + { + "first": 122, + "second": 111, + "amount": 0 + }, + { + "first": 122, + "second": 113, + "amount": 0 + }, + { + "first": 122, + "second": 115, + "amount": 0 + }, + { + "first": 122, + "second": 118, + "amount": 0 + }, + { + "first": 122, + "second": 119, + "amount": 0 + }, + { + "first": 122, + "second": 121, + "amount": 0 + }, + { + "first": 122, + "second": 169, + "amount": 0 + }, + { + "first": 122, + "second": 174, + "amount": 0 + }, + { + "first": 122, + "second": 199, + "amount": 0 + }, + { + "first": 122, + "second": 210, + "amount": 0 + }, + { + "first": 122, + "second": 211, + "amount": 0 + }, + { + "first": 122, + "second": 212, + "amount": 0 + }, + { + "first": 122, + "second": 213, + "amount": 0 + }, + { + "first": 122, + "second": 214, + "amount": 0 + }, + { + "first": 122, + "second": 216, + "amount": 0 + }, + { + "first": 122, + "second": 221, + "amount": -2 + }, + { + "first": 122, + "second": 231, + "amount": 0 + }, + { + "first": 122, + "second": 248, + "amount": 0 + }, + { + "first": 122, + "second": 338, + "amount": 0 + }, + { + "first": 122, + "second": 339, + "amount": 0 + }, + { + "first": 122, + "second": 376, + "amount": -2 + }, + { + "first": 123, + "second": 74, + "amount": -1 + }, + { + "first": 123, + "second": 106, + "amount": 4 + }, + { + "first": 161, + "second": 106, + "amount": 2 + }, + { + "first": 169, + "second": 44, + "amount": -1 + }, + { + "first": 169, + "second": 46, + "amount": -1 + }, + { + "first": 169, + "second": 47, + "amount": -1 + }, + { + "first": 169, + "second": 65, + "amount": -2 + }, + { + "first": 169, + "second": 74, + "amount": 0 + }, + { + "first": 169, + "second": 84, + "amount": -2 + }, + { + "first": 169, + "second": 86, + "amount": -3 + }, + { + "first": 169, + "second": 87, + "amount": -1 + }, + { + "first": 169, + "second": 88, + "amount": -1 + }, + { + "first": 169, + "second": 89, + "amount": -3 + }, + { + "first": 169, + "second": 90, + "amount": -1 + }, + { + "first": 169, + "second": 97, + "amount": 0 + }, + { + "first": 169, + "second": 99, + "amount": 0 + }, + { + "first": 169, + "second": 100, + "amount": 0 + }, + { + "first": 169, + "second": 101, + "amount": 0 + }, + { + "first": 169, + "second": 103, + "amount": 0 + }, + { + "first": 169, + "second": 111, + "amount": 0 + }, + { + "first": 169, + "second": 113, + "amount": 0 + }, + { + "first": 169, + "second": 115, + "amount": 0 + }, + { + "first": 169, + "second": 118, + "amount": 0 + }, + { + "first": 169, + "second": 119, + "amount": 0 + }, + { + "first": 169, + "second": 120, + "amount": 0 + }, + { + "first": 169, + "second": 121, + "amount": 0 + }, + { + "first": 169, + "second": 122, + "amount": 0 + }, + { + "first": 169, + "second": 192, + "amount": -2 + }, + { + "first": 169, + "second": 193, + "amount": -2 + }, + { + "first": 169, + "second": 194, + "amount": -2 + }, + { + "first": 169, + "second": 195, + "amount": -2 + }, + { + "first": 169, + "second": 196, + "amount": -2 + }, + { + "first": 169, + "second": 197, + "amount": -2 + }, + { + "first": 169, + "second": 221, + "amount": -3 + }, + { + "first": 169, + "second": 230, + "amount": 0 + }, + { + "first": 169, + "second": 231, + "amount": 0 + }, + { + "first": 169, + "second": 248, + "amount": 0 + }, + { + "first": 169, + "second": 339, + "amount": 0 + }, + { + "first": 169, + "second": 376, + "amount": -3 + }, + { + "first": 171, + "second": 84, + "amount": -2 + }, + { + "first": 171, + "second": 86, + "amount": -1 + }, + { + "first": 171, + "second": 89, + "amount": -1 + }, + { + "first": 171, + "second": 221, + "amount": -1 + }, + { + "first": 171, + "second": 376, + "amount": -1 + }, + { + "first": 174, + "second": 44, + "amount": -1 + }, + { + "first": 174, + "second": 46, + "amount": -1 + }, + { + "first": 174, + "second": 47, + "amount": -1 + }, + { + "first": 174, + "second": 65, + "amount": -2 + }, + { + "first": 174, + "second": 74, + "amount": 0 + }, + { + "first": 174, + "second": 84, + "amount": -2 + }, + { + "first": 174, + "second": 86, + "amount": -3 + }, + { + "first": 174, + "second": 87, + "amount": -1 + }, + { + "first": 174, + "second": 88, + "amount": -1 + }, + { + "first": 174, + "second": 89, + "amount": -3 + }, + { + "first": 174, + "second": 90, + "amount": -1 + }, + { + "first": 174, + "second": 97, + "amount": 0 + }, + { + "first": 174, + "second": 99, + "amount": 0 + }, + { + "first": 174, + "second": 100, + "amount": 0 + }, + { + "first": 174, + "second": 101, + "amount": 0 + }, + { + "first": 174, + "second": 103, + "amount": 0 + }, + { + "first": 174, + "second": 111, + "amount": 0 + }, + { + "first": 174, + "second": 113, + "amount": 0 + }, + { + "first": 174, + "second": 115, + "amount": 0 + }, + { + "first": 174, + "second": 118, + "amount": 0 + }, + { + "first": 174, + "second": 119, + "amount": 0 + }, + { + "first": 174, + "second": 120, + "amount": 0 + }, + { + "first": 174, + "second": 121, + "amount": 0 + }, + { + "first": 174, + "second": 122, + "amount": 0 + }, + { + "first": 174, + "second": 192, + "amount": -2 + }, + { + "first": 174, + "second": 193, + "amount": -2 + }, + { + "first": 174, + "second": 194, + "amount": -2 + }, + { + "first": 174, + "second": 195, + "amount": -2 + }, + { + "first": 174, + "second": 196, + "amount": -2 + }, + { + "first": 174, + "second": 197, + "amount": -2 + }, + { + "first": 174, + "second": 221, + "amount": -3 + }, + { + "first": 174, + "second": 230, + "amount": 0 + }, + { + "first": 174, + "second": 231, + "amount": 0 + }, + { + "first": 174, + "second": 248, + "amount": 0 + }, + { + "first": 174, + "second": 339, + "amount": 0 + }, + { + "first": 174, + "second": 376, + "amount": -3 + }, + { + "first": 180, + "second": 84, + "amount": -2 + }, + { + "first": 180, + "second": 86, + "amount": -3 + }, + { + "first": 180, + "second": 89, + "amount": -3 + }, + { + "first": 180, + "second": 118, + "amount": 0 + }, + { + "first": 180, + "second": 119, + "amount": 0 + }, + { + "first": 180, + "second": 121, + "amount": 0 + }, + { + "first": 180, + "second": 221, + "amount": -3 + }, + { + "first": 180, + "second": 376, + "amount": -3 + }, + { + "first": 181, + "second": 55, + "amount": -2 + }, + { + "first": 181, + "second": 84, + "amount": -2 + }, + { + "first": 181, + "second": 86, + "amount": -1 + }, + { + "first": 181, + "second": 87, + "amount": -1 + }, + { + "first": 181, + "second": 88, + "amount": 0 + }, + { + "first": 181, + "second": 89, + "amount": -1 + }, + { + "first": 181, + "second": 106, + "amount": 2 + }, + { + "first": 181, + "second": 221, + "amount": -1 + }, + { + "first": 181, + "second": 376, + "amount": -1 + }, + { + "first": 187, + "second": 65, + "amount": 0 + }, + { + "first": 187, + "second": 84, + "amount": -3 + }, + { + "first": 187, + "second": 86, + "amount": -4 + }, + { + "first": 187, + "second": 87, + "amount": -1 + }, + { + "first": 187, + "second": 88, + "amount": -1 + }, + { + "first": 187, + "second": 89, + "amount": -4 + }, + { + "first": 187, + "second": 116, + "amount": 0 + }, + { + "first": 187, + "second": 120, + "amount": -1 + }, + { + "first": 187, + "second": 192, + "amount": 0 + }, + { + "first": 187, + "second": 193, + "amount": 0 + }, + { + "first": 187, + "second": 194, + "amount": 0 + }, + { + "first": 187, + "second": 195, + "amount": 0 + }, + { + "first": 187, + "second": 196, + "amount": 0 + }, + { + "first": 187, + "second": 197, + "amount": 0 + }, + { + "first": 187, + "second": 221, + "amount": -4 + }, + { + "first": 187, + "second": 376, + "amount": -4 + }, + { + "first": 191, + "second": 84, + "amount": -2 + }, + { + "first": 191, + "second": 85, + "amount": -1 + }, + { + "first": 191, + "second": 86, + "amount": -3 + }, + { + "first": 191, + "second": 87, + "amount": -1 + }, + { + "first": 191, + "second": 89, + "amount": -3 + }, + { + "first": 191, + "second": 106, + "amount": 2 + }, + { + "first": 191, + "second": 119, + "amount": -1 + }, + { + "first": 191, + "second": 217, + "amount": -1 + }, + { + "first": 191, + "second": 218, + "amount": -1 + }, + { + "first": 191, + "second": 219, + "amount": -1 + }, + { + "first": 191, + "second": 220, + "amount": -1 + }, + { + "first": 191, + "second": 221, + "amount": -3 + }, + { + "first": 191, + "second": 376, + "amount": -3 + }, + { + "first": 192, + "second": 32, + "amount": -2 + }, + { + "first": 192, + "second": 38, + "amount": -1 + }, + { + "first": 192, + "second": 41, + "amount": -1 + }, + { + "first": 192, + "second": 42, + "amount": -4 + }, + { + "first": 192, + "second": 48, + "amount": -1 + }, + { + "first": 192, + "second": 49, + "amount": 0 + }, + { + "first": 192, + "second": 53, + "amount": 0 + }, + { + "first": 192, + "second": 54, + "amount": -1 + }, + { + "first": 192, + "second": 55, + "amount": -3 + }, + { + "first": 192, + "second": 56, + "amount": -1 + }, + { + "first": 192, + "second": 57, + "amount": -2 + }, + { + "first": 192, + "second": 64, + "amount": -2 + }, + { + "first": 192, + "second": 66, + "amount": -1 + }, + { + "first": 192, + "second": 67, + "amount": -2 + }, + { + "first": 192, + "second": 68, + "amount": -1 + }, + { + "first": 192, + "second": 69, + "amount": -1 + }, + { + "first": 192, + "second": 70, + "amount": -1 + }, + { + "first": 192, + "second": 71, + "amount": -2 + }, + { + "first": 192, + "second": 72, + "amount": -1 + }, + { + "first": 192, + "second": 73, + "amount": -1 + }, + { + "first": 192, + "second": 74, + "amount": 0 + }, + { + "first": 192, + "second": 75, + "amount": -1 + }, + { + "first": 192, + "second": 76, + "amount": -1 + }, + { + "first": 192, + "second": 77, + "amount": -1 + }, + { + "first": 192, + "second": 78, + "amount": -1 + }, + { + "first": 192, + "second": 79, + "amount": -2 + }, + { + "first": 192, + "second": 80, + "amount": -1 + }, + { + "first": 192, + "second": 81, + "amount": -2 + }, + { + "first": 192, + "second": 82, + "amount": -1 + }, + { + "first": 192, + "second": 83, + "amount": -1 + }, + { + "first": 192, + "second": 84, + "amount": -5 + }, + { + "first": 192, + "second": 85, + "amount": -1 + }, + { + "first": 192, + "second": 86, + "amount": -5 + }, + { + "first": 192, + "second": 87, + "amount": -2 + }, + { + "first": 192, + "second": 88, + "amount": -1 + }, + { + "first": 192, + "second": 89, + "amount": -5 + }, + { + "first": 192, + "second": 92, + "amount": -4 + }, + { + "first": 192, + "second": 97, + "amount": 0 + }, + { + "first": 192, + "second": 99, + "amount": 0 + }, + { + "first": 192, + "second": 100, + "amount": 0 + }, + { + "first": 192, + "second": 101, + "amount": 0 + }, + { + "first": 192, + "second": 102, + "amount": 0 + }, + { + "first": 192, + "second": 103, + "amount": 0 + }, + { + "first": 192, + "second": 106, + "amount": 3 + }, + { + "first": 192, + "second": 111, + "amount": 0 + }, + { + "first": 192, + "second": 113, + "amount": 0 + }, + { + "first": 192, + "second": 115, + "amount": 0 + }, + { + "first": 192, + "second": 116, + "amount": -1 + }, + { + "first": 192, + "second": 117, + "amount": 0 + }, + { + "first": 192, + "second": 118, + "amount": -2 + }, + { + "first": 192, + "second": 119, + "amount": -2 + }, + { + "first": 192, + "second": 121, + "amount": -2 + }, + { + "first": 192, + "second": 169, + "amount": -2 + }, + { + "first": 192, + "second": 171, + "amount": 0 + }, + { + "first": 192, + "second": 174, + "amount": -2 + }, + { + "first": 192, + "second": 199, + "amount": -2 + }, + { + "first": 192, + "second": 200, + "amount": -1 + }, + { + "first": 192, + "second": 201, + "amount": -1 + }, + { + "first": 192, + "second": 202, + "amount": -1 + }, + { + "first": 192, + "second": 203, + "amount": -1 + }, + { + "first": 192, + "second": 204, + "amount": -1 + }, + { + "first": 192, + "second": 205, + "amount": -1 + }, + { + "first": 192, + "second": 206, + "amount": -1 + }, + { + "first": 192, + "second": 207, + "amount": -1 + }, + { + "first": 192, + "second": 209, + "amount": -1 + }, + { + "first": 192, + "second": 210, + "amount": -2 + }, + { + "first": 192, + "second": 211, + "amount": -2 + }, + { + "first": 192, + "second": 212, + "amount": -2 + }, + { + "first": 192, + "second": 213, + "amount": -2 + }, + { + "first": 192, + "second": 214, + "amount": -2 + }, + { + "first": 192, + "second": 216, + "amount": -2 + }, + { + "first": 192, + "second": 217, + "amount": -1 + }, + { + "first": 192, + "second": 218, + "amount": -1 + }, + { + "first": 192, + "second": 219, + "amount": -1 + }, + { + "first": 192, + "second": 220, + "amount": -1 + }, + { + "first": 192, + "second": 221, + "amount": -5 + }, + { + "first": 192, + "second": 222, + "amount": -1 + }, + { + "first": 192, + "second": 230, + "amount": 0 + }, + { + "first": 192, + "second": 231, + "amount": 0 + }, + { + "first": 192, + "second": 232, + "amount": 0 + }, + { + "first": 192, + "second": 233, + "amount": 0 + }, + { + "first": 192, + "second": 234, + "amount": 0 + }, + { + "first": 192, + "second": 235, + "amount": 0 + }, + { + "first": 192, + "second": 240, + "amount": 0 + }, + { + "first": 192, + "second": 242, + "amount": 0 + }, + { + "first": 192, + "second": 243, + "amount": 0 + }, + { + "first": 192, + "second": 244, + "amount": 0 + }, + { + "first": 192, + "second": 245, + "amount": 0 + }, + { + "first": 192, + "second": 246, + "amount": 0 + }, + { + "first": 192, + "second": 248, + "amount": 0 + }, + { + "first": 192, + "second": 250, + "amount": 0 + }, + { + "first": 192, + "second": 338, + "amount": -2 + }, + { + "first": 192, + "second": 339, + "amount": 0 + }, + { + "first": 192, + "second": 376, + "amount": -5 + }, + { + "first": 193, + "second": 32, + "amount": -2 + }, + { + "first": 193, + "second": 38, + "amount": -1 + }, + { + "first": 193, + "second": 41, + "amount": -1 + }, + { + "first": 193, + "second": 42, + "amount": -4 + }, + { + "first": 193, + "second": 48, + "amount": -1 + }, + { + "first": 193, + "second": 49, + "amount": 0 + }, + { + "first": 193, + "second": 53, + "amount": 0 + }, + { + "first": 193, + "second": 54, + "amount": -1 + }, + { + "first": 193, + "second": 55, + "amount": -3 + }, + { + "first": 193, + "second": 56, + "amount": -1 + }, + { + "first": 193, + "second": 57, + "amount": -2 + }, + { + "first": 193, + "second": 64, + "amount": -2 + }, + { + "first": 193, + "second": 66, + "amount": -1 + }, + { + "first": 193, + "second": 67, + "amount": -2 + }, + { + "first": 193, + "second": 68, + "amount": -1 + }, + { + "first": 193, + "second": 69, + "amount": -1 + }, + { + "first": 193, + "second": 70, + "amount": -1 + }, + { + "first": 193, + "second": 71, + "amount": -2 + }, + { + "first": 193, + "second": 72, + "amount": -1 + }, + { + "first": 193, + "second": 73, + "amount": -1 + }, + { + "first": 193, + "second": 74, + "amount": 0 + }, + { + "first": 193, + "second": 75, + "amount": -1 + }, + { + "first": 193, + "second": 76, + "amount": -1 + }, + { + "first": 193, + "second": 77, + "amount": -1 + }, + { + "first": 193, + "second": 78, + "amount": -1 + }, + { + "first": 193, + "second": 79, + "amount": -2 + }, + { + "first": 193, + "second": 80, + "amount": -1 + }, + { + "first": 193, + "second": 81, + "amount": -2 + }, + { + "first": 193, + "second": 82, + "amount": -1 + }, + { + "first": 193, + "second": 83, + "amount": -1 + }, + { + "first": 193, + "second": 84, + "amount": -5 + }, + { + "first": 193, + "second": 85, + "amount": -1 + }, + { + "first": 193, + "second": 86, + "amount": -5 + }, + { + "first": 193, + "second": 87, + "amount": -2 + }, + { + "first": 193, + "second": 88, + "amount": -1 + }, + { + "first": 193, + "second": 89, + "amount": -5 + }, + { + "first": 193, + "second": 92, + "amount": -4 + }, + { + "first": 193, + "second": 97, + "amount": 0 + }, + { + "first": 193, + "second": 99, + "amount": 0 + }, + { + "first": 193, + "second": 100, + "amount": 0 + }, + { + "first": 193, + "second": 101, + "amount": 0 + }, + { + "first": 193, + "second": 102, + "amount": 0 + }, + { + "first": 193, + "second": 103, + "amount": 0 + }, + { + "first": 193, + "second": 106, + "amount": 3 + }, + { + "first": 193, + "second": 111, + "amount": 0 + }, + { + "first": 193, + "second": 113, + "amount": 0 + }, + { + "first": 193, + "second": 115, + "amount": 0 + }, + { + "first": 193, + "second": 116, + "amount": -1 + }, + { + "first": 193, + "second": 117, + "amount": 0 + }, + { + "first": 193, + "second": 118, + "amount": -2 + }, + { + "first": 193, + "second": 119, + "amount": -2 + }, + { + "first": 193, + "second": 121, + "amount": -2 + }, + { + "first": 193, + "second": 169, + "amount": -2 + }, + { + "first": 193, + "second": 171, + "amount": 0 + }, + { + "first": 193, + "second": 174, + "amount": -2 + }, + { + "first": 193, + "second": 199, + "amount": -2 + }, + { + "first": 193, + "second": 200, + "amount": -1 + }, + { + "first": 193, + "second": 201, + "amount": -1 + }, + { + "first": 193, + "second": 202, + "amount": -1 + }, + { + "first": 193, + "second": 203, + "amount": -1 + }, + { + "first": 193, + "second": 204, + "amount": -1 + }, + { + "first": 193, + "second": 205, + "amount": -1 + }, + { + "first": 193, + "second": 206, + "amount": -1 + }, + { + "first": 193, + "second": 207, + "amount": -1 + }, + { + "first": 193, + "second": 209, + "amount": -1 + }, + { + "first": 193, + "second": 210, + "amount": -2 + }, + { + "first": 193, + "second": 211, + "amount": -2 + }, + { + "first": 193, + "second": 212, + "amount": -2 + }, + { + "first": 193, + "second": 213, + "amount": -2 + }, + { + "first": 193, + "second": 214, + "amount": -2 + }, + { + "first": 193, + "second": 216, + "amount": -2 + }, + { + "first": 193, + "second": 217, + "amount": -1 + }, + { + "first": 193, + "second": 218, + "amount": -1 + }, + { + "first": 193, + "second": 219, + "amount": -1 + }, + { + "first": 193, + "second": 220, + "amount": -1 + }, + { + "first": 193, + "second": 221, + "amount": -5 + }, + { + "first": 193, + "second": 222, + "amount": -1 + }, + { + "first": 193, + "second": 230, + "amount": 0 + }, + { + "first": 193, + "second": 231, + "amount": 0 + }, + { + "first": 193, + "second": 232, + "amount": 0 + }, + { + "first": 193, + "second": 233, + "amount": 0 + }, + { + "first": 193, + "second": 234, + "amount": 0 + }, + { + "first": 193, + "second": 235, + "amount": 0 + }, + { + "first": 193, + "second": 240, + "amount": 0 + }, + { + "first": 193, + "second": 242, + "amount": 0 + }, + { + "first": 193, + "second": 243, + "amount": 0 + }, + { + "first": 193, + "second": 244, + "amount": 0 + }, + { + "first": 193, + "second": 245, + "amount": 0 + }, + { + "first": 193, + "second": 246, + "amount": 0 + }, + { + "first": 193, + "second": 248, + "amount": 0 + }, + { + "first": 193, + "second": 250, + "amount": 0 + }, + { + "first": 193, + "second": 338, + "amount": -2 + }, + { + "first": 193, + "second": 339, + "amount": 0 + }, + { + "first": 193, + "second": 376, + "amount": -5 + }, + { + "first": 194, + "second": 32, + "amount": -2 + }, + { + "first": 194, + "second": 38, + "amount": -1 + }, + { + "first": 194, + "second": 41, + "amount": -1 + }, + { + "first": 194, + "second": 42, + "amount": -4 + }, + { + "first": 194, + "second": 48, + "amount": -1 + }, + { + "first": 194, + "second": 49, + "amount": 0 + }, + { + "first": 194, + "second": 53, + "amount": 0 + }, + { + "first": 194, + "second": 54, + "amount": -1 + }, + { + "first": 194, + "second": 55, + "amount": -3 + }, + { + "first": 194, + "second": 56, + "amount": -1 + }, + { + "first": 194, + "second": 57, + "amount": -2 + }, + { + "first": 194, + "second": 64, + "amount": -2 + }, + { + "first": 194, + "second": 66, + "amount": -1 + }, + { + "first": 194, + "second": 67, + "amount": -2 + }, + { + "first": 194, + "second": 68, + "amount": -1 + }, + { + "first": 194, + "second": 69, + "amount": -1 + }, + { + "first": 194, + "second": 70, + "amount": -1 + }, + { + "first": 194, + "second": 71, + "amount": -2 + }, + { + "first": 194, + "second": 72, + "amount": -1 + }, + { + "first": 194, + "second": 73, + "amount": -1 + }, + { + "first": 194, + "second": 74, + "amount": 0 + }, + { + "first": 194, + "second": 75, + "amount": -1 + }, + { + "first": 194, + "second": 76, + "amount": -1 + }, + { + "first": 194, + "second": 77, + "amount": -1 + }, + { + "first": 194, + "second": 78, + "amount": -1 + }, + { + "first": 194, + "second": 79, + "amount": -2 + }, + { + "first": 194, + "second": 80, + "amount": -1 + }, + { + "first": 194, + "second": 81, + "amount": -2 + }, + { + "first": 194, + "second": 82, + "amount": -1 + }, + { + "first": 194, + "second": 83, + "amount": -1 + }, + { + "first": 194, + "second": 84, + "amount": -5 + }, + { + "first": 194, + "second": 85, + "amount": -1 + }, + { + "first": 194, + "second": 86, + "amount": -5 + }, + { + "first": 194, + "second": 87, + "amount": -2 + }, + { + "first": 194, + "second": 88, + "amount": -1 + }, + { + "first": 194, + "second": 89, + "amount": -5 + }, + { + "first": 194, + "second": 92, + "amount": -4 + }, + { + "first": 194, + "second": 97, + "amount": 0 + }, + { + "first": 194, + "second": 99, + "amount": 0 + }, + { + "first": 194, + "second": 100, + "amount": 0 + }, + { + "first": 194, + "second": 101, + "amount": 0 + }, + { + "first": 194, + "second": 102, + "amount": 0 + }, + { + "first": 194, + "second": 103, + "amount": 0 + }, + { + "first": 194, + "second": 106, + "amount": 3 + }, + { + "first": 194, + "second": 111, + "amount": 0 + }, + { + "first": 194, + "second": 113, + "amount": 0 + }, + { + "first": 194, + "second": 115, + "amount": 0 + }, + { + "first": 194, + "second": 116, + "amount": -1 + }, + { + "first": 194, + "second": 117, + "amount": 0 + }, + { + "first": 194, + "second": 118, + "amount": -2 + }, + { + "first": 194, + "second": 119, + "amount": -2 + }, + { + "first": 194, + "second": 121, + "amount": -2 + }, + { + "first": 194, + "second": 169, + "amount": -2 + }, + { + "first": 194, + "second": 171, + "amount": 0 + }, + { + "first": 194, + "second": 174, + "amount": -2 + }, + { + "first": 194, + "second": 199, + "amount": -2 + }, + { + "first": 194, + "second": 200, + "amount": -1 + }, + { + "first": 194, + "second": 201, + "amount": -1 + }, + { + "first": 194, + "second": 202, + "amount": -1 + }, + { + "first": 194, + "second": 203, + "amount": -1 + }, + { + "first": 194, + "second": 204, + "amount": -1 + }, + { + "first": 194, + "second": 205, + "amount": -1 + }, + { + "first": 194, + "second": 206, + "amount": -1 + }, + { + "first": 194, + "second": 207, + "amount": -1 + }, + { + "first": 194, + "second": 209, + "amount": -1 + }, + { + "first": 194, + "second": 210, + "amount": -2 + }, + { + "first": 194, + "second": 211, + "amount": -2 + }, + { + "first": 194, + "second": 212, + "amount": -2 + }, + { + "first": 194, + "second": 213, + "amount": -2 + }, + { + "first": 194, + "second": 214, + "amount": -2 + }, + { + "first": 194, + "second": 216, + "amount": -2 + }, + { + "first": 194, + "second": 217, + "amount": -1 + }, + { + "first": 194, + "second": 218, + "amount": -1 + }, + { + "first": 194, + "second": 219, + "amount": -1 + }, + { + "first": 194, + "second": 220, + "amount": -1 + }, + { + "first": 194, + "second": 221, + "amount": -5 + }, + { + "first": 194, + "second": 222, + "amount": -1 + }, + { + "first": 194, + "second": 230, + "amount": 0 + }, + { + "first": 194, + "second": 231, + "amount": 0 + }, + { + "first": 194, + "second": 232, + "amount": 0 + }, + { + "first": 194, + "second": 233, + "amount": 0 + }, + { + "first": 194, + "second": 234, + "amount": 0 + }, + { + "first": 194, + "second": 235, + "amount": 0 + }, + { + "first": 194, + "second": 240, + "amount": 0 + }, + { + "first": 194, + "second": 242, + "amount": 0 + }, + { + "first": 194, + "second": 243, + "amount": 0 + }, + { + "first": 194, + "second": 244, + "amount": 0 + }, + { + "first": 194, + "second": 245, + "amount": 0 + }, + { + "first": 194, + "second": 246, + "amount": 0 + }, + { + "first": 194, + "second": 248, + "amount": 0 + }, + { + "first": 194, + "second": 250, + "amount": 0 + }, + { + "first": 194, + "second": 338, + "amount": -2 + }, + { + "first": 194, + "second": 339, + "amount": 0 + }, + { + "first": 194, + "second": 376, + "amount": -5 + }, + { + "first": 195, + "second": 32, + "amount": -2 + }, + { + "first": 195, + "second": 38, + "amount": -1 + }, + { + "first": 195, + "second": 41, + "amount": -1 + }, + { + "first": 195, + "second": 42, + "amount": -4 + }, + { + "first": 195, + "second": 48, + "amount": -1 + }, + { + "first": 195, + "second": 49, + "amount": 0 + }, + { + "first": 195, + "second": 53, + "amount": 0 + }, + { + "first": 195, + "second": 54, + "amount": -1 + }, + { + "first": 195, + "second": 55, + "amount": -3 + }, + { + "first": 195, + "second": 56, + "amount": -1 + }, + { + "first": 195, + "second": 57, + "amount": -2 + }, + { + "first": 195, + "second": 64, + "amount": -2 + }, + { + "first": 195, + "second": 66, + "amount": -1 + }, + { + "first": 195, + "second": 67, + "amount": -2 + }, + { + "first": 195, + "second": 68, + "amount": -1 + }, + { + "first": 195, + "second": 69, + "amount": -1 + }, + { + "first": 195, + "second": 70, + "amount": -1 + }, + { + "first": 195, + "second": 71, + "amount": -2 + }, + { + "first": 195, + "second": 72, + "amount": -1 + }, + { + "first": 195, + "second": 73, + "amount": -1 + }, + { + "first": 195, + "second": 74, + "amount": 0 + }, + { + "first": 195, + "second": 75, + "amount": -1 + }, + { + "first": 195, + "second": 76, + "amount": -1 + }, + { + "first": 195, + "second": 77, + "amount": -1 + }, + { + "first": 195, + "second": 78, + "amount": -1 + }, + { + "first": 195, + "second": 79, + "amount": -2 + }, + { + "first": 195, + "second": 80, + "amount": -1 + }, + { + "first": 195, + "second": 81, + "amount": -2 + }, + { + "first": 195, + "second": 82, + "amount": -1 + }, + { + "first": 195, + "second": 83, + "amount": -1 + }, + { + "first": 195, + "second": 84, + "amount": -5 + }, + { + "first": 195, + "second": 85, + "amount": -1 + }, + { + "first": 195, + "second": 86, + "amount": -5 + }, + { + "first": 195, + "second": 87, + "amount": -2 + }, + { + "first": 195, + "second": 88, + "amount": -1 + }, + { + "first": 195, + "second": 89, + "amount": -5 + }, + { + "first": 195, + "second": 92, + "amount": -4 + }, + { + "first": 195, + "second": 97, + "amount": 0 + }, + { + "first": 195, + "second": 99, + "amount": 0 + }, + { + "first": 195, + "second": 100, + "amount": 0 + }, + { + "first": 195, + "second": 101, + "amount": 0 + }, + { + "first": 195, + "second": 102, + "amount": 0 + }, + { + "first": 195, + "second": 103, + "amount": 0 + }, + { + "first": 195, + "second": 106, + "amount": 3 + }, + { + "first": 195, + "second": 111, + "amount": 0 + }, + { + "first": 195, + "second": 113, + "amount": 0 + }, + { + "first": 195, + "second": 115, + "amount": 0 + }, + { + "first": 195, + "second": 116, + "amount": -1 + }, + { + "first": 195, + "second": 117, + "amount": 0 + }, + { + "first": 195, + "second": 118, + "amount": -2 + }, + { + "first": 195, + "second": 119, + "amount": -2 + }, + { + "first": 195, + "second": 121, + "amount": -2 + }, + { + "first": 195, + "second": 169, + "amount": -2 + }, + { + "first": 195, + "second": 171, + "amount": 0 + }, + { + "first": 195, + "second": 174, + "amount": -2 + }, + { + "first": 195, + "second": 199, + "amount": -2 + }, + { + "first": 195, + "second": 200, + "amount": -1 + }, + { + "first": 195, + "second": 201, + "amount": -1 + }, + { + "first": 195, + "second": 202, + "amount": -1 + }, + { + "first": 195, + "second": 203, + "amount": -1 + }, + { + "first": 195, + "second": 204, + "amount": -1 + }, + { + "first": 195, + "second": 205, + "amount": -1 + }, + { + "first": 195, + "second": 206, + "amount": -1 + }, + { + "first": 195, + "second": 207, + "amount": -1 + }, + { + "first": 195, + "second": 209, + "amount": -1 + }, + { + "first": 195, + "second": 210, + "amount": -2 + }, + { + "first": 195, + "second": 211, + "amount": -2 + }, + { + "first": 195, + "second": 212, + "amount": -2 + }, + { + "first": 195, + "second": 213, + "amount": -2 + }, + { + "first": 195, + "second": 214, + "amount": -2 + }, + { + "first": 195, + "second": 216, + "amount": -2 + }, + { + "first": 195, + "second": 217, + "amount": -1 + }, + { + "first": 195, + "second": 218, + "amount": -1 + }, + { + "first": 195, + "second": 219, + "amount": -1 + }, + { + "first": 195, + "second": 220, + "amount": -1 + }, + { + "first": 195, + "second": 221, + "amount": -5 + }, + { + "first": 195, + "second": 222, + "amount": -1 + }, + { + "first": 195, + "second": 230, + "amount": 0 + }, + { + "first": 195, + "second": 231, + "amount": 0 + }, + { + "first": 195, + "second": 232, + "amount": 0 + }, + { + "first": 195, + "second": 233, + "amount": 0 + }, + { + "first": 195, + "second": 234, + "amount": 0 + }, + { + "first": 195, + "second": 235, + "amount": 0 + }, + { + "first": 195, + "second": 240, + "amount": 0 + }, + { + "first": 195, + "second": 242, + "amount": 0 + }, + { + "first": 195, + "second": 243, + "amount": 0 + }, + { + "first": 195, + "second": 244, + "amount": 0 + }, + { + "first": 195, + "second": 245, + "amount": 0 + }, + { + "first": 195, + "second": 246, + "amount": 0 + }, + { + "first": 195, + "second": 248, + "amount": 0 + }, + { + "first": 195, + "second": 250, + "amount": 0 + }, + { + "first": 195, + "second": 338, + "amount": -2 + }, + { + "first": 195, + "second": 339, + "amount": 0 + }, + { + "first": 195, + "second": 376, + "amount": -5 + }, + { + "first": 196, + "second": 32, + "amount": -2 + }, + { + "first": 196, + "second": 38, + "amount": -1 + }, + { + "first": 196, + "second": 41, + "amount": -1 + }, + { + "first": 196, + "second": 42, + "amount": -4 + }, + { + "first": 196, + "second": 48, + "amount": -1 + }, + { + "first": 196, + "second": 49, + "amount": 0 + }, + { + "first": 196, + "second": 53, + "amount": 0 + }, + { + "first": 196, + "second": 54, + "amount": -1 + }, + { + "first": 196, + "second": 55, + "amount": -3 + }, + { + "first": 196, + "second": 56, + "amount": -1 + }, + { + "first": 196, + "second": 57, + "amount": -2 + }, + { + "first": 196, + "second": 64, + "amount": -2 + }, + { + "first": 196, + "second": 66, + "amount": -1 + }, + { + "first": 196, + "second": 67, + "amount": -2 + }, + { + "first": 196, + "second": 68, + "amount": -1 + }, + { + "first": 196, + "second": 69, + "amount": -1 + }, + { + "first": 196, + "second": 70, + "amount": -1 + }, + { + "first": 196, + "second": 71, + "amount": -2 + }, + { + "first": 196, + "second": 72, + "amount": -1 + }, + { + "first": 196, + "second": 73, + "amount": -1 + }, + { + "first": 196, + "second": 74, + "amount": 0 + }, + { + "first": 196, + "second": 75, + "amount": -1 + }, + { + "first": 196, + "second": 76, + "amount": -1 + }, + { + "first": 196, + "second": 77, + "amount": -1 + }, + { + "first": 196, + "second": 78, + "amount": -1 + }, + { + "first": 196, + "second": 79, + "amount": -2 + }, + { + "first": 196, + "second": 80, + "amount": -1 + }, + { + "first": 196, + "second": 81, + "amount": -2 + }, + { + "first": 196, + "second": 82, + "amount": -1 + }, + { + "first": 196, + "second": 83, + "amount": -1 + }, + { + "first": 196, + "second": 84, + "amount": -5 + }, + { + "first": 196, + "second": 85, + "amount": -1 + }, + { + "first": 196, + "second": 86, + "amount": -5 + }, + { + "first": 196, + "second": 87, + "amount": -2 + }, + { + "first": 196, + "second": 88, + "amount": -1 + }, + { + "first": 196, + "second": 89, + "amount": -5 + }, + { + "first": 196, + "second": 92, + "amount": -4 + }, + { + "first": 196, + "second": 97, + "amount": 0 + }, + { + "first": 196, + "second": 99, + "amount": 0 + }, + { + "first": 196, + "second": 100, + "amount": 0 + }, + { + "first": 196, + "second": 101, + "amount": 0 + }, + { + "first": 196, + "second": 102, + "amount": 0 + }, + { + "first": 196, + "second": 103, + "amount": 0 + }, + { + "first": 196, + "second": 106, + "amount": 3 + }, + { + "first": 196, + "second": 111, + "amount": 0 + }, + { + "first": 196, + "second": 113, + "amount": 0 + }, + { + "first": 196, + "second": 115, + "amount": 0 + }, + { + "first": 196, + "second": 116, + "amount": -1 + }, + { + "first": 196, + "second": 117, + "amount": 0 + }, + { + "first": 196, + "second": 118, + "amount": -2 + }, + { + "first": 196, + "second": 119, + "amount": -2 + }, + { + "first": 196, + "second": 121, + "amount": -2 + }, + { + "first": 196, + "second": 169, + "amount": -2 + }, + { + "first": 196, + "second": 171, + "amount": 0 + }, + { + "first": 196, + "second": 174, + "amount": -2 + }, + { + "first": 196, + "second": 199, + "amount": -2 + }, + { + "first": 196, + "second": 200, + "amount": -1 + }, + { + "first": 196, + "second": 201, + "amount": -1 + }, + { + "first": 196, + "second": 202, + "amount": -1 + }, + { + "first": 196, + "second": 203, + "amount": -1 + }, + { + "first": 196, + "second": 204, + "amount": -1 + }, + { + "first": 196, + "second": 205, + "amount": -1 + }, + { + "first": 196, + "second": 206, + "amount": -1 + }, + { + "first": 196, + "second": 207, + "amount": -1 + }, + { + "first": 196, + "second": 209, + "amount": -1 + }, + { + "first": 196, + "second": 210, + "amount": -2 + }, + { + "first": 196, + "second": 211, + "amount": -2 + }, + { + "first": 196, + "second": 212, + "amount": -2 + }, + { + "first": 196, + "second": 213, + "amount": -2 + }, + { + "first": 196, + "second": 214, + "amount": -2 + }, + { + "first": 196, + "second": 216, + "amount": -2 + }, + { + "first": 196, + "second": 217, + "amount": -1 + }, + { + "first": 196, + "second": 218, + "amount": -1 + }, + { + "first": 196, + "second": 219, + "amount": -1 + }, + { + "first": 196, + "second": 220, + "amount": -1 + }, + { + "first": 196, + "second": 221, + "amount": -5 + }, + { + "first": 196, + "second": 222, + "amount": -1 + }, + { + "first": 196, + "second": 230, + "amount": 0 + }, + { + "first": 196, + "second": 231, + "amount": 0 + }, + { + "first": 196, + "second": 232, + "amount": 0 + }, + { + "first": 196, + "second": 233, + "amount": 0 + }, + { + "first": 196, + "second": 234, + "amount": 0 + }, + { + "first": 196, + "second": 235, + "amount": 0 + }, + { + "first": 196, + "second": 240, + "amount": 0 + }, + { + "first": 196, + "second": 242, + "amount": 0 + }, + { + "first": 196, + "second": 243, + "amount": 0 + }, + { + "first": 196, + "second": 244, + "amount": 0 + }, + { + "first": 196, + "second": 245, + "amount": 0 + }, + { + "first": 196, + "second": 246, + "amount": 0 + }, + { + "first": 196, + "second": 248, + "amount": 0 + }, + { + "first": 196, + "second": 250, + "amount": 0 + }, + { + "first": 196, + "second": 338, + "amount": -2 + }, + { + "first": 196, + "second": 339, + "amount": 0 + }, + { + "first": 196, + "second": 376, + "amount": -5 + }, + { + "first": 197, + "second": 32, + "amount": -2 + }, + { + "first": 197, + "second": 38, + "amount": -1 + }, + { + "first": 197, + "second": 41, + "amount": -1 + }, + { + "first": 197, + "second": 42, + "amount": -4 + }, + { + "first": 197, + "second": 48, + "amount": -1 + }, + { + "first": 197, + "second": 49, + "amount": 0 + }, + { + "first": 197, + "second": 53, + "amount": 0 + }, + { + "first": 197, + "second": 54, + "amount": -1 + }, + { + "first": 197, + "second": 55, + "amount": -3 + }, + { + "first": 197, + "second": 56, + "amount": -1 + }, + { + "first": 197, + "second": 57, + "amount": -2 + }, + { + "first": 197, + "second": 64, + "amount": -2 + }, + { + "first": 197, + "second": 66, + "amount": -1 + }, + { + "first": 197, + "second": 67, + "amount": -2 + }, + { + "first": 197, + "second": 68, + "amount": -1 + }, + { + "first": 197, + "second": 69, + "amount": -1 + }, + { + "first": 197, + "second": 70, + "amount": -1 + }, + { + "first": 197, + "second": 71, + "amount": -2 + }, + { + "first": 197, + "second": 72, + "amount": -1 + }, + { + "first": 197, + "second": 73, + "amount": -1 + }, + { + "first": 197, + "second": 74, + "amount": 0 + }, + { + "first": 197, + "second": 75, + "amount": -1 + }, + { + "first": 197, + "second": 76, + "amount": -1 + }, + { + "first": 197, + "second": 77, + "amount": -1 + }, + { + "first": 197, + "second": 78, + "amount": -1 + }, + { + "first": 197, + "second": 79, + "amount": -2 + }, + { + "first": 197, + "second": 80, + "amount": -1 + }, + { + "first": 197, + "second": 81, + "amount": -2 + }, + { + "first": 197, + "second": 82, + "amount": -1 + }, + { + "first": 197, + "second": 83, + "amount": -1 + }, + { + "first": 197, + "second": 84, + "amount": -5 + }, + { + "first": 197, + "second": 85, + "amount": -1 + }, + { + "first": 197, + "second": 86, + "amount": -5 + }, + { + "first": 197, + "second": 87, + "amount": -2 + }, + { + "first": 197, + "second": 88, + "amount": -1 + }, + { + "first": 197, + "second": 89, + "amount": -5 + }, + { + "first": 197, + "second": 92, + "amount": -4 + }, + { + "first": 197, + "second": 97, + "amount": 0 + }, + { + "first": 197, + "second": 99, + "amount": 0 + }, + { + "first": 197, + "second": 100, + "amount": 0 + }, + { + "first": 197, + "second": 101, + "amount": 0 + }, + { + "first": 197, + "second": 102, + "amount": 0 + }, + { + "first": 197, + "second": 103, + "amount": 0 + }, + { + "first": 197, + "second": 106, + "amount": 3 + }, + { + "first": 197, + "second": 111, + "amount": 0 + }, + { + "first": 197, + "second": 113, + "amount": 0 + }, + { + "first": 197, + "second": 115, + "amount": 0 + }, + { + "first": 197, + "second": 116, + "amount": -1 + }, + { + "first": 197, + "second": 117, + "amount": 0 + }, + { + "first": 197, + "second": 118, + "amount": -2 + }, + { + "first": 197, + "second": 119, + "amount": -2 + }, + { + "first": 197, + "second": 121, + "amount": -2 + }, + { + "first": 197, + "second": 169, + "amount": -2 + }, + { + "first": 197, + "second": 171, + "amount": 0 + }, + { + "first": 197, + "second": 174, + "amount": -2 + }, + { + "first": 197, + "second": 199, + "amount": -2 + }, + { + "first": 197, + "second": 200, + "amount": -1 + }, + { + "first": 197, + "second": 201, + "amount": -1 + }, + { + "first": 197, + "second": 202, + "amount": -1 + }, + { + "first": 197, + "second": 203, + "amount": -1 + }, + { + "first": 197, + "second": 204, + "amount": -1 + }, + { + "first": 197, + "second": 205, + "amount": -1 + }, + { + "first": 197, + "second": 206, + "amount": -1 + }, + { + "first": 197, + "second": 207, + "amount": -1 + }, + { + "first": 197, + "second": 209, + "amount": -1 + }, + { + "first": 197, + "second": 210, + "amount": -2 + }, + { + "first": 197, + "second": 211, + "amount": -2 + }, + { + "first": 197, + "second": 212, + "amount": -2 + }, + { + "first": 197, + "second": 213, + "amount": -2 + }, + { + "first": 197, + "second": 214, + "amount": -2 + }, + { + "first": 197, + "second": 216, + "amount": -2 + }, + { + "first": 197, + "second": 217, + "amount": -1 + }, + { + "first": 197, + "second": 218, + "amount": -1 + }, + { + "first": 197, + "second": 219, + "amount": -1 + }, + { + "first": 197, + "second": 220, + "amount": -1 + }, + { + "first": 197, + "second": 221, + "amount": -5 + }, + { + "first": 197, + "second": 222, + "amount": -1 + }, + { + "first": 197, + "second": 230, + "amount": 0 + }, + { + "first": 197, + "second": 231, + "amount": 0 + }, + { + "first": 197, + "second": 232, + "amount": 0 + }, + { + "first": 197, + "second": 233, + "amount": 0 + }, + { + "first": 197, + "second": 234, + "amount": 0 + }, + { + "first": 197, + "second": 235, + "amount": 0 + }, + { + "first": 197, + "second": 240, + "amount": 0 + }, + { + "first": 197, + "second": 242, + "amount": 0 + }, + { + "first": 197, + "second": 243, + "amount": 0 + }, + { + "first": 197, + "second": 244, + "amount": 0 + }, + { + "first": 197, + "second": 245, + "amount": 0 + }, + { + "first": 197, + "second": 246, + "amount": 0 + }, + { + "first": 197, + "second": 248, + "amount": 0 + }, + { + "first": 197, + "second": 250, + "amount": 0 + }, + { + "first": 197, + "second": 338, + "amount": -2 + }, + { + "first": 197, + "second": 339, + "amount": 0 + }, + { + "first": 197, + "second": 376, + "amount": -5 + }, + { + "first": 198, + "second": 38, + "amount": 0 + }, + { + "first": 198, + "second": 64, + "amount": -1 + }, + { + "first": 198, + "second": 67, + "amount": -1 + }, + { + "first": 198, + "second": 71, + "amount": -1 + }, + { + "first": 198, + "second": 74, + "amount": 0 + }, + { + "first": 198, + "second": 79, + "amount": -1 + }, + { + "first": 198, + "second": 81, + "amount": -1 + }, + { + "first": 198, + "second": 87, + "amount": 0 + }, + { + "first": 198, + "second": 97, + "amount": 0 + }, + { + "first": 198, + "second": 99, + "amount": 0 + }, + { + "first": 198, + "second": 100, + "amount": 0 + }, + { + "first": 198, + "second": 101, + "amount": 0 + }, + { + "first": 198, + "second": 102, + "amount": -1 + }, + { + "first": 198, + "second": 103, + "amount": 0 + }, + { + "first": 198, + "second": 106, + "amount": 1 + }, + { + "first": 198, + "second": 111, + "amount": 0 + }, + { + "first": 198, + "second": 113, + "amount": 0 + }, + { + "first": 198, + "second": 115, + "amount": 0 + }, + { + "first": 198, + "second": 116, + "amount": 0 + }, + { + "first": 198, + "second": 118, + "amount": -1 + }, + { + "first": 198, + "second": 119, + "amount": -1 + }, + { + "first": 198, + "second": 121, + "amount": -1 + }, + { + "first": 198, + "second": 169, + "amount": -1 + }, + { + "first": 198, + "second": 171, + "amount": -1 + }, + { + "first": 198, + "second": 174, + "amount": -1 + }, + { + "first": 198, + "second": 180, + "amount": 0 + }, + { + "first": 198, + "second": 199, + "amount": -1 + }, + { + "first": 198, + "second": 210, + "amount": -1 + }, + { + "first": 198, + "second": 211, + "amount": -1 + }, + { + "first": 198, + "second": 212, + "amount": -1 + }, + { + "first": 198, + "second": 213, + "amount": -1 + }, + { + "first": 198, + "second": 214, + "amount": -1 + }, + { + "first": 198, + "second": 216, + "amount": -1 + }, + { + "first": 198, + "second": 224, + "amount": 0 + }, + { + "first": 198, + "second": 225, + "amount": 0 + }, + { + "first": 198, + "second": 226, + "amount": 0 + }, + { + "first": 198, + "second": 227, + "amount": 0 + }, + { + "first": 198, + "second": 228, + "amount": 0 + }, + { + "first": 198, + "second": 229, + "amount": 0 + }, + { + "first": 198, + "second": 230, + "amount": 0 + }, + { + "first": 198, + "second": 231, + "amount": 0 + }, + { + "first": 198, + "second": 248, + "amount": 0 + }, + { + "first": 198, + "second": 338, + "amount": -1 + }, + { + "first": 198, + "second": 339, + "amount": 0 + }, + { + "first": 199, + "second": 47, + "amount": -1 + }, + { + "first": 199, + "second": 64, + "amount": 0 + }, + { + "first": 199, + "second": 65, + "amount": -1 + }, + { + "first": 199, + "second": 67, + "amount": 0 + }, + { + "first": 199, + "second": 71, + "amount": 0 + }, + { + "first": 199, + "second": 74, + "amount": -1 + }, + { + "first": 199, + "second": 79, + "amount": 0 + }, + { + "first": 199, + "second": 81, + "amount": 0 + }, + { + "first": 199, + "second": 84, + "amount": 0 + }, + { + "first": 199, + "second": 86, + "amount": 0 + }, + { + "first": 199, + "second": 87, + "amount": 0 + }, + { + "first": 199, + "second": 88, + "amount": -1 + }, + { + "first": 199, + "second": 89, + "amount": 0 + }, + { + "first": 199, + "second": 90, + "amount": -1 + }, + { + "first": 199, + "second": 99, + "amount": 0 + }, + { + "first": 199, + "second": 100, + "amount": 0 + }, + { + "first": 199, + "second": 101, + "amount": 0 + }, + { + "first": 199, + "second": 103, + "amount": 0 + }, + { + "first": 199, + "second": 111, + "amount": 0 + }, + { + "first": 199, + "second": 113, + "amount": 0 + }, + { + "first": 199, + "second": 115, + "amount": 0 + }, + { + "first": 199, + "second": 118, + "amount": 0 + }, + { + "first": 199, + "second": 119, + "amount": 0 + }, + { + "first": 199, + "second": 120, + "amount": -1 + }, + { + "first": 199, + "second": 121, + "amount": 0 + }, + { + "first": 199, + "second": 122, + "amount": 0 + }, + { + "first": 199, + "second": 169, + "amount": 0 + }, + { + "first": 199, + "second": 171, + "amount": 0 + }, + { + "first": 199, + "second": 174, + "amount": 0 + }, + { + "first": 199, + "second": 187, + "amount": 0 + }, + { + "first": 199, + "second": 192, + "amount": -1 + }, + { + "first": 199, + "second": 193, + "amount": -1 + }, + { + "first": 199, + "second": 194, + "amount": -1 + }, + { + "first": 199, + "second": 195, + "amount": -1 + }, + { + "first": 199, + "second": 196, + "amount": -1 + }, + { + "first": 199, + "second": 197, + "amount": -1 + }, + { + "first": 199, + "second": 199, + "amount": 0 + }, + { + "first": 199, + "second": 210, + "amount": 0 + }, + { + "first": 199, + "second": 211, + "amount": 0 + }, + { + "first": 199, + "second": 212, + "amount": 0 + }, + { + "first": 199, + "second": 213, + "amount": 0 + }, + { + "first": 199, + "second": 214, + "amount": 0 + }, + { + "first": 199, + "second": 216, + "amount": 0 + }, + { + "first": 199, + "second": 221, + "amount": 0 + }, + { + "first": 199, + "second": 231, + "amount": 0 + }, + { + "first": 199, + "second": 248, + "amount": 0 + }, + { + "first": 199, + "second": 338, + "amount": 0 + }, + { + "first": 199, + "second": 339, + "amount": 0 + }, + { + "first": 199, + "second": 376, + "amount": 0 + }, + { + "first": 200, + "second": 38, + "amount": 0 + }, + { + "first": 200, + "second": 64, + "amount": -1 + }, + { + "first": 200, + "second": 67, + "amount": -1 + }, + { + "first": 200, + "second": 71, + "amount": -1 + }, + { + "first": 200, + "second": 74, + "amount": 0 + }, + { + "first": 200, + "second": 79, + "amount": -1 + }, + { + "first": 200, + "second": 81, + "amount": -1 + }, + { + "first": 200, + "second": 87, + "amount": 0 + }, + { + "first": 200, + "second": 97, + "amount": 0 + }, + { + "first": 200, + "second": 99, + "amount": 0 + }, + { + "first": 200, + "second": 100, + "amount": 0 + }, + { + "first": 200, + "second": 101, + "amount": 0 + }, + { + "first": 200, + "second": 102, + "amount": -1 + }, + { + "first": 200, + "second": 103, + "amount": 0 + }, + { + "first": 200, + "second": 106, + "amount": 1 + }, + { + "first": 200, + "second": 111, + "amount": 0 + }, + { + "first": 200, + "second": 113, + "amount": 0 + }, + { + "first": 200, + "second": 115, + "amount": 0 + }, + { + "first": 200, + "second": 116, + "amount": 0 + }, + { + "first": 200, + "second": 118, + "amount": -1 + }, + { + "first": 200, + "second": 119, + "amount": -1 + }, + { + "first": 200, + "second": 121, + "amount": -1 + }, + { + "first": 200, + "second": 169, + "amount": -1 + }, + { + "first": 200, + "second": 171, + "amount": -1 + }, + { + "first": 200, + "second": 174, + "amount": -1 + }, + { + "first": 200, + "second": 180, + "amount": 0 + }, + { + "first": 200, + "second": 199, + "amount": -1 + }, + { + "first": 200, + "second": 210, + "amount": -1 + }, + { + "first": 200, + "second": 211, + "amount": -1 + }, + { + "first": 200, + "second": 212, + "amount": -1 + }, + { + "first": 200, + "second": 213, + "amount": -1 + }, + { + "first": 200, + "second": 214, + "amount": -1 + }, + { + "first": 200, + "second": 216, + "amount": -1 + }, + { + "first": 200, + "second": 224, + "amount": 0 + }, + { + "first": 200, + "second": 225, + "amount": 0 + }, + { + "first": 200, + "second": 226, + "amount": 0 + }, + { + "first": 200, + "second": 227, + "amount": 0 + }, + { + "first": 200, + "second": 228, + "amount": 0 + }, + { + "first": 200, + "second": 229, + "amount": 0 + }, + { + "first": 200, + "second": 230, + "amount": 0 + }, + { + "first": 200, + "second": 231, + "amount": 0 + }, + { + "first": 200, + "second": 248, + "amount": 0 + }, + { + "first": 200, + "second": 338, + "amount": -1 + }, + { + "first": 200, + "second": 339, + "amount": 0 + }, + { + "first": 201, + "second": 38, + "amount": 0 + }, + { + "first": 201, + "second": 64, + "amount": -1 + }, + { + "first": 201, + "second": 67, + "amount": -1 + }, + { + "first": 201, + "second": 71, + "amount": -1 + }, + { + "first": 201, + "second": 74, + "amount": 0 + }, + { + "first": 201, + "second": 79, + "amount": -1 + }, + { + "first": 201, + "second": 81, + "amount": -1 + }, + { + "first": 201, + "second": 87, + "amount": 0 + }, + { + "first": 201, + "second": 97, + "amount": 0 + }, + { + "first": 201, + "second": 99, + "amount": 0 + }, + { + "first": 201, + "second": 100, + "amount": 0 + }, + { + "first": 201, + "second": 101, + "amount": 0 + }, + { + "first": 201, + "second": 102, + "amount": -1 + }, + { + "first": 201, + "second": 103, + "amount": 0 + }, + { + "first": 201, + "second": 106, + "amount": 1 + }, + { + "first": 201, + "second": 111, + "amount": 0 + }, + { + "first": 201, + "second": 113, + "amount": 0 + }, + { + "first": 201, + "second": 115, + "amount": 0 + }, + { + "first": 201, + "second": 116, + "amount": 0 + }, + { + "first": 201, + "second": 118, + "amount": -1 + }, + { + "first": 201, + "second": 119, + "amount": -1 + }, + { + "first": 201, + "second": 121, + "amount": -1 + }, + { + "first": 201, + "second": 169, + "amount": -1 + }, + { + "first": 201, + "second": 171, + "amount": -1 + }, + { + "first": 201, + "second": 174, + "amount": -1 + }, + { + "first": 201, + "second": 180, + "amount": 0 + }, + { + "first": 201, + "second": 199, + "amount": -1 + }, + { + "first": 201, + "second": 210, + "amount": -1 + }, + { + "first": 201, + "second": 211, + "amount": -1 + }, + { + "first": 201, + "second": 212, + "amount": -1 + }, + { + "first": 201, + "second": 213, + "amount": -1 + }, + { + "first": 201, + "second": 214, + "amount": -1 + }, + { + "first": 201, + "second": 216, + "amount": -1 + }, + { + "first": 201, + "second": 224, + "amount": 0 + }, + { + "first": 201, + "second": 225, + "amount": 0 + }, + { + "first": 201, + "second": 226, + "amount": 0 + }, + { + "first": 201, + "second": 227, + "amount": 0 + }, + { + "first": 201, + "second": 228, + "amount": 0 + }, + { + "first": 201, + "second": 229, + "amount": 0 + }, + { + "first": 201, + "second": 230, + "amount": 0 + }, + { + "first": 201, + "second": 231, + "amount": 0 + }, + { + "first": 201, + "second": 248, + "amount": 0 + }, + { + "first": 201, + "second": 338, + "amount": -1 + }, + { + "first": 201, + "second": 339, + "amount": 0 + }, + { + "first": 202, + "second": 38, + "amount": 0 + }, + { + "first": 202, + "second": 64, + "amount": -1 + }, + { + "first": 202, + "second": 67, + "amount": -1 + }, + { + "first": 202, + "second": 71, + "amount": -1 + }, + { + "first": 202, + "second": 74, + "amount": 0 + }, + { + "first": 202, + "second": 79, + "amount": -1 + }, + { + "first": 202, + "second": 81, + "amount": -1 + }, + { + "first": 202, + "second": 87, + "amount": 0 + }, + { + "first": 202, + "second": 97, + "amount": 0 + }, + { + "first": 202, + "second": 99, + "amount": 0 + }, + { + "first": 202, + "second": 100, + "amount": 0 + }, + { + "first": 202, + "second": 101, + "amount": 0 + }, + { + "first": 202, + "second": 102, + "amount": -1 + }, + { + "first": 202, + "second": 103, + "amount": 0 + }, + { + "first": 202, + "second": 106, + "amount": 1 + }, + { + "first": 202, + "second": 111, + "amount": 0 + }, + { + "first": 202, + "second": 113, + "amount": 0 + }, + { + "first": 202, + "second": 115, + "amount": 0 + }, + { + "first": 202, + "second": 116, + "amount": 0 + }, + { + "first": 202, + "second": 118, + "amount": -1 + }, + { + "first": 202, + "second": 119, + "amount": -1 + }, + { + "first": 202, + "second": 121, + "amount": -1 + }, + { + "first": 202, + "second": 169, + "amount": -1 + }, + { + "first": 202, + "second": 171, + "amount": -1 + }, + { + "first": 202, + "second": 174, + "amount": -1 + }, + { + "first": 202, + "second": 180, + "amount": 0 + }, + { + "first": 202, + "second": 199, + "amount": -1 + }, + { + "first": 202, + "second": 210, + "amount": -1 + }, + { + "first": 202, + "second": 211, + "amount": -1 + }, + { + "first": 202, + "second": 212, + "amount": -1 + }, + { + "first": 202, + "second": 213, + "amount": -1 + }, + { + "first": 202, + "second": 214, + "amount": -1 + }, + { + "first": 202, + "second": 216, + "amount": -1 + }, + { + "first": 202, + "second": 224, + "amount": 0 + }, + { + "first": 202, + "second": 225, + "amount": 0 + }, + { + "first": 202, + "second": 226, + "amount": 0 + }, + { + "first": 202, + "second": 227, + "amount": 0 + }, + { + "first": 202, + "second": 228, + "amount": 0 + }, + { + "first": 202, + "second": 229, + "amount": 0 + }, + { + "first": 202, + "second": 230, + "amount": 0 + }, + { + "first": 202, + "second": 231, + "amount": 0 + }, + { + "first": 202, + "second": 248, + "amount": 0 + }, + { + "first": 202, + "second": 338, + "amount": -1 + }, + { + "first": 202, + "second": 339, + "amount": 0 + }, + { + "first": 203, + "second": 38, + "amount": 0 + }, + { + "first": 203, + "second": 64, + "amount": -1 + }, + { + "first": 203, + "second": 67, + "amount": -1 + }, + { + "first": 203, + "second": 71, + "amount": -1 + }, + { + "first": 203, + "second": 74, + "amount": 0 + }, + { + "first": 203, + "second": 79, + "amount": -1 + }, + { + "first": 203, + "second": 81, + "amount": -1 + }, + { + "first": 203, + "second": 87, + "amount": 0 + }, + { + "first": 203, + "second": 97, + "amount": 0 + }, + { + "first": 203, + "second": 99, + "amount": 0 + }, + { + "first": 203, + "second": 100, + "amount": 0 + }, + { + "first": 203, + "second": 101, + "amount": 0 + }, + { + "first": 203, + "second": 102, + "amount": -1 + }, + { + "first": 203, + "second": 103, + "amount": 0 + }, + { + "first": 203, + "second": 106, + "amount": 1 + }, + { + "first": 203, + "second": 111, + "amount": 0 + }, + { + "first": 203, + "second": 113, + "amount": 0 + }, + { + "first": 203, + "second": 115, + "amount": 0 + }, + { + "first": 203, + "second": 116, + "amount": 0 + }, + { + "first": 203, + "second": 118, + "amount": -1 + }, + { + "first": 203, + "second": 119, + "amount": -1 + }, + { + "first": 203, + "second": 121, + "amount": -1 + }, + { + "first": 203, + "second": 169, + "amount": -1 + }, + { + "first": 203, + "second": 171, + "amount": -1 + }, + { + "first": 203, + "second": 174, + "amount": -1 + }, + { + "first": 203, + "second": 180, + "amount": 0 + }, + { + "first": 203, + "second": 199, + "amount": -1 + }, + { + "first": 203, + "second": 210, + "amount": -1 + }, + { + "first": 203, + "second": 211, + "amount": -1 + }, + { + "first": 203, + "second": 212, + "amount": -1 + }, + { + "first": 203, + "second": 213, + "amount": -1 + }, + { + "first": 203, + "second": 214, + "amount": -1 + }, + { + "first": 203, + "second": 216, + "amount": -1 + }, + { + "first": 203, + "second": 224, + "amount": 0 + }, + { + "first": 203, + "second": 225, + "amount": 0 + }, + { + "first": 203, + "second": 226, + "amount": 0 + }, + { + "first": 203, + "second": 227, + "amount": 0 + }, + { + "first": 203, + "second": 228, + "amount": 0 + }, + { + "first": 203, + "second": 229, + "amount": 0 + }, + { + "first": 203, + "second": 230, + "amount": 0 + }, + { + "first": 203, + "second": 231, + "amount": 0 + }, + { + "first": 203, + "second": 248, + "amount": 0 + }, + { + "first": 203, + "second": 338, + "amount": -1 + }, + { + "first": 203, + "second": 339, + "amount": 0 + }, + { + "first": 204, + "second": 65, + "amount": -1 + }, + { + "first": 204, + "second": 74, + "amount": 0 + }, + { + "first": 204, + "second": 86, + "amount": -1 + }, + { + "first": 204, + "second": 87, + "amount": -1 + }, + { + "first": 204, + "second": 89, + "amount": -1 + }, + { + "first": 204, + "second": 192, + "amount": -1 + }, + { + "first": 204, + "second": 193, + "amount": -1 + }, + { + "first": 204, + "second": 194, + "amount": -1 + }, + { + "first": 204, + "second": 195, + "amount": -1 + }, + { + "first": 204, + "second": 196, + "amount": -1 + }, + { + "first": 204, + "second": 197, + "amount": -1 + }, + { + "first": 204, + "second": 221, + "amount": -1 + }, + { + "first": 204, + "second": 376, + "amount": -1 + }, + { + "first": 205, + "second": 65, + "amount": -1 + }, + { + "first": 205, + "second": 74, + "amount": 0 + }, + { + "first": 205, + "second": 86, + "amount": -1 + }, + { + "first": 205, + "second": 87, + "amount": -1 + }, + { + "first": 205, + "second": 89, + "amount": -1 + }, + { + "first": 205, + "second": 192, + "amount": -1 + }, + { + "first": 205, + "second": 193, + "amount": -1 + }, + { + "first": 205, + "second": 194, + "amount": -1 + }, + { + "first": 205, + "second": 195, + "amount": -1 + }, + { + "first": 205, + "second": 196, + "amount": -1 + }, + { + "first": 205, + "second": 197, + "amount": -1 + }, + { + "first": 205, + "second": 221, + "amount": -1 + }, + { + "first": 205, + "second": 376, + "amount": -1 + }, + { + "first": 206, + "second": 65, + "amount": -1 + }, + { + "first": 206, + "second": 74, + "amount": 0 + }, + { + "first": 206, + "second": 86, + "amount": -1 + }, + { + "first": 206, + "second": 87, + "amount": -1 + }, + { + "first": 206, + "second": 89, + "amount": -1 + }, + { + "first": 206, + "second": 192, + "amount": -1 + }, + { + "first": 206, + "second": 193, + "amount": -1 + }, + { + "first": 206, + "second": 194, + "amount": -1 + }, + { + "first": 206, + "second": 195, + "amount": -1 + }, + { + "first": 206, + "second": 196, + "amount": -1 + }, + { + "first": 206, + "second": 197, + "amount": -1 + }, + { + "first": 206, + "second": 221, + "amount": -1 + }, + { + "first": 206, + "second": 376, + "amount": -1 + }, + { + "first": 207, + "second": 65, + "amount": -1 + }, + { + "first": 207, + "second": 74, + "amount": 0 + }, + { + "first": 207, + "second": 86, + "amount": -1 + }, + { + "first": 207, + "second": 87, + "amount": -1 + }, + { + "first": 207, + "second": 89, + "amount": -1 + }, + { + "first": 207, + "second": 192, + "amount": -1 + }, + { + "first": 207, + "second": 193, + "amount": -1 + }, + { + "first": 207, + "second": 194, + "amount": -1 + }, + { + "first": 207, + "second": 195, + "amount": -1 + }, + { + "first": 207, + "second": 196, + "amount": -1 + }, + { + "first": 207, + "second": 197, + "amount": -1 + }, + { + "first": 207, + "second": 221, + "amount": -1 + }, + { + "first": 207, + "second": 376, + "amount": -1 + }, + { + "first": 208, + "second": 44, + "amount": -1 + }, + { + "first": 208, + "second": 46, + "amount": -1 + }, + { + "first": 208, + "second": 47, + "amount": -1 + }, + { + "first": 208, + "second": 65, + "amount": -2 + }, + { + "first": 208, + "second": 74, + "amount": 0 + }, + { + "first": 208, + "second": 84, + "amount": -2 + }, + { + "first": 208, + "second": 86, + "amount": -3 + }, + { + "first": 208, + "second": 87, + "amount": -1 + }, + { + "first": 208, + "second": 88, + "amount": -1 + }, + { + "first": 208, + "second": 89, + "amount": -3 + }, + { + "first": 208, + "second": 90, + "amount": -1 + }, + { + "first": 208, + "second": 97, + "amount": 0 + }, + { + "first": 208, + "second": 99, + "amount": 0 + }, + { + "first": 208, + "second": 100, + "amount": 0 + }, + { + "first": 208, + "second": 101, + "amount": 0 + }, + { + "first": 208, + "second": 103, + "amount": 0 + }, + { + "first": 208, + "second": 111, + "amount": 0 + }, + { + "first": 208, + "second": 113, + "amount": 0 + }, + { + "first": 208, + "second": 115, + "amount": 0 + }, + { + "first": 208, + "second": 118, + "amount": 0 + }, + { + "first": 208, + "second": 119, + "amount": 0 + }, + { + "first": 208, + "second": 120, + "amount": 0 + }, + { + "first": 208, + "second": 121, + "amount": 0 + }, + { + "first": 208, + "second": 122, + "amount": 0 + }, + { + "first": 208, + "second": 192, + "amount": -2 + }, + { + "first": 208, + "second": 193, + "amount": -2 + }, + { + "first": 208, + "second": 194, + "amount": -2 + }, + { + "first": 208, + "second": 195, + "amount": -2 + }, + { + "first": 208, + "second": 196, + "amount": -2 + }, + { + "first": 208, + "second": 197, + "amount": -2 + }, + { + "first": 208, + "second": 221, + "amount": -3 + }, + { + "first": 208, + "second": 230, + "amount": 0 + }, + { + "first": 208, + "second": 231, + "amount": 0 + }, + { + "first": 208, + "second": 248, + "amount": 0 + }, + { + "first": 208, + "second": 339, + "amount": 0 + }, + { + "first": 208, + "second": 376, + "amount": -3 + }, + { + "first": 209, + "second": 65, + "amount": -1 + }, + { + "first": 209, + "second": 74, + "amount": 0 + }, + { + "first": 209, + "second": 86, + "amount": -1 + }, + { + "first": 209, + "second": 87, + "amount": -1 + }, + { + "first": 209, + "second": 89, + "amount": -1 + }, + { + "first": 209, + "second": 192, + "amount": -1 + }, + { + "first": 209, + "second": 193, + "amount": -1 + }, + { + "first": 209, + "second": 194, + "amount": -1 + }, + { + "first": 209, + "second": 195, + "amount": -1 + }, + { + "first": 209, + "second": 196, + "amount": -1 + }, + { + "first": 209, + "second": 197, + "amount": -1 + }, + { + "first": 209, + "second": 221, + "amount": -1 + }, + { + "first": 209, + "second": 376, + "amount": -1 + }, + { + "first": 210, + "second": 44, + "amount": -1 + }, + { + "first": 210, + "second": 46, + "amount": -1 + }, + { + "first": 210, + "second": 47, + "amount": -1 + }, + { + "first": 210, + "second": 65, + "amount": -2 + }, + { + "first": 210, + "second": 74, + "amount": 0 + }, + { + "first": 210, + "second": 84, + "amount": -2 + }, + { + "first": 210, + "second": 86, + "amount": -3 + }, + { + "first": 210, + "second": 87, + "amount": -1 + }, + { + "first": 210, + "second": 88, + "amount": -1 + }, + { + "first": 210, + "second": 89, + "amount": -3 + }, + { + "first": 210, + "second": 90, + "amount": -1 + }, + { + "first": 210, + "second": 97, + "amount": 0 + }, + { + "first": 210, + "second": 99, + "amount": 0 + }, + { + "first": 210, + "second": 100, + "amount": 0 + }, + { + "first": 210, + "second": 101, + "amount": 0 + }, + { + "first": 210, + "second": 103, + "amount": 0 + }, + { + "first": 210, + "second": 111, + "amount": 0 + }, + { + "first": 210, + "second": 113, + "amount": 0 + }, + { + "first": 210, + "second": 115, + "amount": 0 + }, + { + "first": 210, + "second": 118, + "amount": 0 + }, + { + "first": 210, + "second": 119, + "amount": 0 + }, + { + "first": 210, + "second": 120, + "amount": 0 + }, + { + "first": 210, + "second": 121, + "amount": 0 + }, + { + "first": 210, + "second": 122, + "amount": 0 + }, + { + "first": 210, + "second": 192, + "amount": -2 + }, + { + "first": 210, + "second": 193, + "amount": -2 + }, + { + "first": 210, + "second": 194, + "amount": -2 + }, + { + "first": 210, + "second": 195, + "amount": -2 + }, + { + "first": 210, + "second": 196, + "amount": -2 + }, + { + "first": 210, + "second": 197, + "amount": -2 + }, + { + "first": 210, + "second": 221, + "amount": -3 + }, + { + "first": 210, + "second": 230, + "amount": 0 + }, + { + "first": 210, + "second": 231, + "amount": 0 + }, + { + "first": 210, + "second": 248, + "amount": 0 + }, + { + "first": 210, + "second": 339, + "amount": 0 + }, + { + "first": 210, + "second": 376, + "amount": -3 + }, + { + "first": 211, + "second": 44, + "amount": -1 + }, + { + "first": 211, + "second": 46, + "amount": -1 + }, + { + "first": 211, + "second": 47, + "amount": -1 + }, + { + "first": 211, + "second": 65, + "amount": -2 + }, + { + "first": 211, + "second": 74, + "amount": 0 + }, + { + "first": 211, + "second": 84, + "amount": -2 + }, + { + "first": 211, + "second": 86, + "amount": -3 + }, + { + "first": 211, + "second": 87, + "amount": -1 + }, + { + "first": 211, + "second": 88, + "amount": -1 + }, + { + "first": 211, + "second": 89, + "amount": -3 + }, + { + "first": 211, + "second": 90, + "amount": -1 + }, + { + "first": 211, + "second": 97, + "amount": 0 + }, + { + "first": 211, + "second": 99, + "amount": 0 + }, + { + "first": 211, + "second": 100, + "amount": 0 + }, + { + "first": 211, + "second": 101, + "amount": 0 + }, + { + "first": 211, + "second": 103, + "amount": 0 + }, + { + "first": 211, + "second": 111, + "amount": 0 + }, + { + "first": 211, + "second": 113, + "amount": 0 + }, + { + "first": 211, + "second": 115, + "amount": 0 + }, + { + "first": 211, + "second": 118, + "amount": 0 + }, + { + "first": 211, + "second": 119, + "amount": 0 + }, + { + "first": 211, + "second": 120, + "amount": 0 + }, + { + "first": 211, + "second": 121, + "amount": 0 + }, + { + "first": 211, + "second": 122, + "amount": 0 + }, + { + "first": 211, + "second": 192, + "amount": -2 + }, + { + "first": 211, + "second": 193, + "amount": -2 + }, + { + "first": 211, + "second": 194, + "amount": -2 + }, + { + "first": 211, + "second": 195, + "amount": -2 + }, + { + "first": 211, + "second": 196, + "amount": -2 + }, + { + "first": 211, + "second": 197, + "amount": -2 + }, + { + "first": 211, + "second": 221, + "amount": -3 + }, + { + "first": 211, + "second": 230, + "amount": 0 + }, + { + "first": 211, + "second": 231, + "amount": 0 + }, + { + "first": 211, + "second": 248, + "amount": 0 + }, + { + "first": 211, + "second": 339, + "amount": 0 + }, + { + "first": 211, + "second": 376, + "amount": -3 + }, + { + "first": 212, + "second": 44, + "amount": -1 + }, + { + "first": 212, + "second": 46, + "amount": -1 + }, + { + "first": 212, + "second": 47, + "amount": -1 + }, + { + "first": 212, + "second": 65, + "amount": -2 + }, + { + "first": 212, + "second": 74, + "amount": 0 + }, + { + "first": 212, + "second": 84, + "amount": -2 + }, + { + "first": 212, + "second": 86, + "amount": -3 + }, + { + "first": 212, + "second": 87, + "amount": -1 + }, + { + "first": 212, + "second": 88, + "amount": -1 + }, + { + "first": 212, + "second": 89, + "amount": -3 + }, + { + "first": 212, + "second": 90, + "amount": -1 + }, + { + "first": 212, + "second": 97, + "amount": 0 + }, + { + "first": 212, + "second": 99, + "amount": 0 + }, + { + "first": 212, + "second": 100, + "amount": 0 + }, + { + "first": 212, + "second": 101, + "amount": 0 + }, + { + "first": 212, + "second": 103, + "amount": 0 + }, + { + "first": 212, + "second": 111, + "amount": 0 + }, + { + "first": 212, + "second": 113, + "amount": 0 + }, + { + "first": 212, + "second": 115, + "amount": 0 + }, + { + "first": 212, + "second": 118, + "amount": 0 + }, + { + "first": 212, + "second": 119, + "amount": 0 + }, + { + "first": 212, + "second": 120, + "amount": 0 + }, + { + "first": 212, + "second": 121, + "amount": 0 + }, + { + "first": 212, + "second": 122, + "amount": 0 + }, + { + "first": 212, + "second": 192, + "amount": -2 + }, + { + "first": 212, + "second": 193, + "amount": -2 + }, + { + "first": 212, + "second": 194, + "amount": -2 + }, + { + "first": 212, + "second": 195, + "amount": -2 + }, + { + "first": 212, + "second": 196, + "amount": -2 + }, + { + "first": 212, + "second": 197, + "amount": -2 + }, + { + "first": 212, + "second": 221, + "amount": -3 + }, + { + "first": 212, + "second": 230, + "amount": 0 + }, + { + "first": 212, + "second": 231, + "amount": 0 + }, + { + "first": 212, + "second": 248, + "amount": 0 + }, + { + "first": 212, + "second": 339, + "amount": 0 + }, + { + "first": 212, + "second": 376, + "amount": -3 + }, + { + "first": 213, + "second": 44, + "amount": -1 + }, + { + "first": 213, + "second": 46, + "amount": -1 + }, + { + "first": 213, + "second": 47, + "amount": -1 + }, + { + "first": 213, + "second": 65, + "amount": -2 + }, + { + "first": 213, + "second": 74, + "amount": 0 + }, + { + "first": 213, + "second": 84, + "amount": -2 + }, + { + "first": 213, + "second": 86, + "amount": -3 + }, + { + "first": 213, + "second": 87, + "amount": -1 + }, + { + "first": 213, + "second": 88, + "amount": -1 + }, + { + "first": 213, + "second": 89, + "amount": -3 + }, + { + "first": 213, + "second": 90, + "amount": -1 + }, + { + "first": 213, + "second": 97, + "amount": 0 + }, + { + "first": 213, + "second": 99, + "amount": 0 + }, + { + "first": 213, + "second": 100, + "amount": 0 + }, + { + "first": 213, + "second": 101, + "amount": 0 + }, + { + "first": 213, + "second": 103, + "amount": 0 + }, + { + "first": 213, + "second": 111, + "amount": 0 + }, + { + "first": 213, + "second": 113, + "amount": 0 + }, + { + "first": 213, + "second": 115, + "amount": 0 + }, + { + "first": 213, + "second": 118, + "amount": 0 + }, + { + "first": 213, + "second": 119, + "amount": 0 + }, + { + "first": 213, + "second": 120, + "amount": 0 + }, + { + "first": 213, + "second": 121, + "amount": 0 + }, + { + "first": 213, + "second": 122, + "amount": 0 + }, + { + "first": 213, + "second": 192, + "amount": -2 + }, + { + "first": 213, + "second": 193, + "amount": -2 + }, + { + "first": 213, + "second": 194, + "amount": -2 + }, + { + "first": 213, + "second": 195, + "amount": -2 + }, + { + "first": 213, + "second": 196, + "amount": -2 + }, + { + "first": 213, + "second": 197, + "amount": -2 + }, + { + "first": 213, + "second": 221, + "amount": -3 + }, + { + "first": 213, + "second": 230, + "amount": 0 + }, + { + "first": 213, + "second": 231, + "amount": 0 + }, + { + "first": 213, + "second": 248, + "amount": 0 + }, + { + "first": 213, + "second": 339, + "amount": 0 + }, + { + "first": 213, + "second": 376, + "amount": -3 + }, + { + "first": 214, + "second": 44, + "amount": -1 + }, + { + "first": 214, + "second": 46, + "amount": -1 + }, + { + "first": 214, + "second": 47, + "amount": -1 + }, + { + "first": 214, + "second": 65, + "amount": -2 + }, + { + "first": 214, + "second": 74, + "amount": 0 + }, + { + "first": 214, + "second": 84, + "amount": -2 + }, + { + "first": 214, + "second": 86, + "amount": -3 + }, + { + "first": 214, + "second": 87, + "amount": -1 + }, + { + "first": 214, + "second": 88, + "amount": -1 + }, + { + "first": 214, + "second": 89, + "amount": -3 + }, + { + "first": 214, + "second": 90, + "amount": -1 + }, + { + "first": 214, + "second": 97, + "amount": 0 + }, + { + "first": 214, + "second": 99, + "amount": 0 + }, + { + "first": 214, + "second": 100, + "amount": 0 + }, + { + "first": 214, + "second": 101, + "amount": 0 + }, + { + "first": 214, + "second": 103, + "amount": 0 + }, + { + "first": 214, + "second": 111, + "amount": 0 + }, + { + "first": 214, + "second": 113, + "amount": 0 + }, + { + "first": 214, + "second": 115, + "amount": 0 + }, + { + "first": 214, + "second": 118, + "amount": 0 + }, + { + "first": 214, + "second": 119, + "amount": 0 + }, + { + "first": 214, + "second": 120, + "amount": 0 + }, + { + "first": 214, + "second": 121, + "amount": 0 + }, + { + "first": 214, + "second": 122, + "amount": 0 + }, + { + "first": 214, + "second": 192, + "amount": -2 + }, + { + "first": 214, + "second": 193, + "amount": -2 + }, + { + "first": 214, + "second": 194, + "amount": -2 + }, + { + "first": 214, + "second": 195, + "amount": -2 + }, + { + "first": 214, + "second": 196, + "amount": -2 + }, + { + "first": 214, + "second": 197, + "amount": -2 + }, + { + "first": 214, + "second": 221, + "amount": -3 + }, + { + "first": 214, + "second": 230, + "amount": 0 + }, + { + "first": 214, + "second": 231, + "amount": 0 + }, + { + "first": 214, + "second": 248, + "amount": 0 + }, + { + "first": 214, + "second": 339, + "amount": 0 + }, + { + "first": 214, + "second": 376, + "amount": -3 + }, + { + "first": 216, + "second": 44, + "amount": -1 + }, + { + "first": 216, + "second": 46, + "amount": -1 + }, + { + "first": 216, + "second": 47, + "amount": -1 + }, + { + "first": 216, + "second": 65, + "amount": -2 + }, + { + "first": 216, + "second": 74, + "amount": 0 + }, + { + "first": 216, + "second": 84, + "amount": -2 + }, + { + "first": 216, + "second": 86, + "amount": -3 + }, + { + "first": 216, + "second": 87, + "amount": -1 + }, + { + "first": 216, + "second": 88, + "amount": -1 + }, + { + "first": 216, + "second": 89, + "amount": -3 + }, + { + "first": 216, + "second": 90, + "amount": -1 + }, + { + "first": 216, + "second": 97, + "amount": 0 + }, + { + "first": 216, + "second": 99, + "amount": 0 + }, + { + "first": 216, + "second": 100, + "amount": 0 + }, + { + "first": 216, + "second": 101, + "amount": 0 + }, + { + "first": 216, + "second": 103, + "amount": 0 + }, + { + "first": 216, + "second": 111, + "amount": 0 + }, + { + "first": 216, + "second": 113, + "amount": 0 + }, + { + "first": 216, + "second": 115, + "amount": 0 + }, + { + "first": 216, + "second": 118, + "amount": 0 + }, + { + "first": 216, + "second": 119, + "amount": 0 + }, + { + "first": 216, + "second": 120, + "amount": 0 + }, + { + "first": 216, + "second": 121, + "amount": 0 + }, + { + "first": 216, + "second": 122, + "amount": 0 + }, + { + "first": 216, + "second": 192, + "amount": -2 + }, + { + "first": 216, + "second": 193, + "amount": -2 + }, + { + "first": 216, + "second": 194, + "amount": -2 + }, + { + "first": 216, + "second": 195, + "amount": -2 + }, + { + "first": 216, + "second": 196, + "amount": -2 + }, + { + "first": 216, + "second": 197, + "amount": -2 + }, + { + "first": 216, + "second": 221, + "amount": -3 + }, + { + "first": 216, + "second": 230, + "amount": 0 + }, + { + "first": 216, + "second": 231, + "amount": 0 + }, + { + "first": 216, + "second": 248, + "amount": 0 + }, + { + "first": 216, + "second": 339, + "amount": 0 + }, + { + "first": 216, + "second": 376, + "amount": -3 + }, + { + "first": 217, + "second": 44, + "amount": -2 + }, + { + "first": 217, + "second": 46, + "amount": -2 + }, + { + "first": 217, + "second": 47, + "amount": -2 + }, + { + "first": 217, + "second": 65, + "amount": -1 + }, + { + "first": 217, + "second": 88, + "amount": 0 + }, + { + "first": 217, + "second": 115, + "amount": 0 + }, + { + "first": 217, + "second": 120, + "amount": 0 + }, + { + "first": 217, + "second": 192, + "amount": -1 + }, + { + "first": 217, + "second": 193, + "amount": -1 + }, + { + "first": 217, + "second": 194, + "amount": -1 + }, + { + "first": 217, + "second": 195, + "amount": -1 + }, + { + "first": 217, + "second": 196, + "amount": -1 + }, + { + "first": 217, + "second": 197, + "amount": -1 + }, + { + "first": 218, + "second": 44, + "amount": -2 + }, + { + "first": 218, + "second": 46, + "amount": -2 + }, + { + "first": 218, + "second": 47, + "amount": -2 + }, + { + "first": 218, + "second": 65, + "amount": -1 + }, + { + "first": 218, + "second": 88, + "amount": 0 + }, + { + "first": 218, + "second": 115, + "amount": 0 + }, + { + "first": 218, + "second": 120, + "amount": 0 + }, + { + "first": 218, + "second": 192, + "amount": -1 + }, + { + "first": 218, + "second": 193, + "amount": -1 + }, + { + "first": 218, + "second": 194, + "amount": -1 + }, + { + "first": 218, + "second": 195, + "amount": -1 + }, + { + "first": 218, + "second": 196, + "amount": -1 + }, + { + "first": 218, + "second": 197, + "amount": -1 + }, + { + "first": 219, + "second": 44, + "amount": -2 + }, + { + "first": 219, + "second": 46, + "amount": -2 + }, + { + "first": 219, + "second": 47, + "amount": -2 + }, + { + "first": 219, + "second": 65, + "amount": -1 + }, + { + "first": 219, + "second": 88, + "amount": 0 + }, + { + "first": 219, + "second": 115, + "amount": 0 + }, + { + "first": 219, + "second": 120, + "amount": 0 + }, + { + "first": 219, + "second": 192, + "amount": -1 + }, + { + "first": 219, + "second": 193, + "amount": -1 + }, + { + "first": 219, + "second": 194, + "amount": -1 + }, + { + "first": 219, + "second": 195, + "amount": -1 + }, + { + "first": 219, + "second": 196, + "amount": -1 + }, + { + "first": 219, + "second": 197, + "amount": -1 + }, + { + "first": 220, + "second": 44, + "amount": -2 + }, + { + "first": 220, + "second": 46, + "amount": -2 + }, + { + "first": 220, + "second": 47, + "amount": -2 + }, + { + "first": 220, + "second": 65, + "amount": -1 + }, + { + "first": 220, + "second": 88, + "amount": 0 + }, + { + "first": 220, + "second": 115, + "amount": 0 + }, + { + "first": 220, + "second": 120, + "amount": 0 + }, + { + "first": 220, + "second": 192, + "amount": -1 + }, + { + "first": 220, + "second": 193, + "amount": -1 + }, + { + "first": 220, + "second": 194, + "amount": -1 + }, + { + "first": 220, + "second": 195, + "amount": -1 + }, + { + "first": 220, + "second": 196, + "amount": -1 + }, + { + "first": 220, + "second": 197, + "amount": -1 + }, + { + "first": 221, + "second": 32, + "amount": -2 + }, + { + "first": 221, + "second": 38, + "amount": -3 + }, + { + "first": 221, + "second": 41, + "amount": -1 + }, + { + "first": 221, + "second": 44, + "amount": -5 + }, + { + "first": 221, + "second": 45, + "amount": -1 + }, + { + "first": 221, + "second": 46, + "amount": -5 + }, + { + "first": 221, + "second": 47, + "amount": -6 + }, + { + "first": 221, + "second": 48, + "amount": -2 + }, + { + "first": 221, + "second": 50, + "amount": -1 + }, + { + "first": 221, + "second": 51, + "amount": -1 + }, + { + "first": 221, + "second": 52, + "amount": -4 + }, + { + "first": 221, + "second": 53, + "amount": -1 + }, + { + "first": 221, + "second": 54, + "amount": -3 + }, + { + "first": 221, + "second": 55, + "amount": 0 + }, + { + "first": 221, + "second": 56, + "amount": -3 + }, + { + "first": 221, + "second": 57, + "amount": -2 + }, + { + "first": 221, + "second": 58, + "amount": -4 + }, + { + "first": 221, + "second": 59, + "amount": -4 + }, + { + "first": 221, + "second": 64, + "amount": -3 + }, + { + "first": 221, + "second": 65, + "amount": -5 + }, + { + "first": 221, + "second": 66, + "amount": -1 + }, + { + "first": 221, + "second": 67, + "amount": -3 + }, + { + "first": 221, + "second": 68, + "amount": -1 + }, + { + "first": 221, + "second": 69, + "amount": -1 + }, + { + "first": 221, + "second": 70, + "amount": -1 + }, + { + "first": 221, + "second": 71, + "amount": -3 + }, + { + "first": 221, + "second": 72, + "amount": -1 + }, + { + "first": 221, + "second": 73, + "amount": -1 + }, + { + "first": 221, + "second": 74, + "amount": -6 + }, + { + "first": 221, + "second": 75, + "amount": -1 + }, + { + "first": 221, + "second": 76, + "amount": -1 + }, + { + "first": 221, + "second": 77, + "amount": -1 + }, + { + "first": 221, + "second": 78, + "amount": -1 + }, + { + "first": 221, + "second": 79, + "amount": -3 + }, + { + "first": 221, + "second": 80, + "amount": -1 + }, + { + "first": 221, + "second": 81, + "amount": -3 + }, + { + "first": 221, + "second": 82, + "amount": -1 + }, + { + "first": 221, + "second": 83, + "amount": -1 + }, + { + "first": 221, + "second": 88, + "amount": -1 + }, + { + "first": 221, + "second": 97, + "amount": -4 + }, + { + "first": 221, + "second": 99, + "amount": -4 + }, + { + "first": 221, + "second": 100, + "amount": -4 + }, + { + "first": 221, + "second": 101, + "amount": -4 + }, + { + "first": 221, + "second": 102, + "amount": -1 + }, + { + "first": 221, + "second": 103, + "amount": -4 + }, + { + "first": 221, + "second": 109, + "amount": -1 + }, + { + "first": 221, + "second": 110, + "amount": -1 + }, + { + "first": 221, + "second": 111, + "amount": -4 + }, + { + "first": 221, + "second": 112, + "amount": -1 + }, + { + "first": 221, + "second": 113, + "amount": -4 + }, + { + "first": 221, + "second": 114, + "amount": -1 + }, + { + "first": 221, + "second": 115, + "amount": -3 + }, + { + "first": 221, + "second": 117, + "amount": -1 + }, + { + "first": 221, + "second": 118, + "amount": -2 + }, + { + "first": 221, + "second": 119, + "amount": -1 + }, + { + "first": 221, + "second": 120, + "amount": -3 + }, + { + "first": 221, + "second": 121, + "amount": -2 + }, + { + "first": 221, + "second": 122, + "amount": -2 + }, + { + "first": 221, + "second": 169, + "amount": -3 + }, + { + "first": 221, + "second": 171, + "amount": -4 + }, + { + "first": 221, + "second": 174, + "amount": -3 + }, + { + "first": 221, + "second": 180, + "amount": -3 + }, + { + "first": 221, + "second": 181, + "amount": -1 + }, + { + "first": 221, + "second": 187, + "amount": -1 + }, + { + "first": 221, + "second": 192, + "amount": -5 + }, + { + "first": 221, + "second": 193, + "amount": -5 + }, + { + "first": 221, + "second": 194, + "amount": -5 + }, + { + "first": 221, + "second": 195, + "amount": -5 + }, + { + "first": 221, + "second": 196, + "amount": -5 + }, + { + "first": 221, + "second": 197, + "amount": -5 + }, + { + "first": 221, + "second": 198, + "amount": -5 + }, + { + "first": 221, + "second": 199, + "amount": -3 + }, + { + "first": 221, + "second": 200, + "amount": -1 + }, + { + "first": 221, + "second": 201, + "amount": -1 + }, + { + "first": 221, + "second": 202, + "amount": -1 + }, + { + "first": 221, + "second": 203, + "amount": -1 + }, + { + "first": 221, + "second": 204, + "amount": -1 + }, + { + "first": 221, + "second": 205, + "amount": -1 + }, + { + "first": 221, + "second": 206, + "amount": -1 + }, + { + "first": 221, + "second": 207, + "amount": -1 + }, + { + "first": 221, + "second": 209, + "amount": -1 + }, + { + "first": 221, + "second": 210, + "amount": -3 + }, + { + "first": 221, + "second": 211, + "amount": -3 + }, + { + "first": 221, + "second": 212, + "amount": -3 + }, + { + "first": 221, + "second": 213, + "amount": -3 + }, + { + "first": 221, + "second": 214, + "amount": -3 + }, + { + "first": 221, + "second": 216, + "amount": -3 + }, + { + "first": 221, + "second": 222, + "amount": -1 + }, + { + "first": 221, + "second": 224, + "amount": -3 + }, + { + "first": 221, + "second": 225, + "amount": -3 + }, + { + "first": 221, + "second": 226, + "amount": -3 + }, + { + "first": 221, + "second": 227, + "amount": -3 + }, + { + "first": 221, + "second": 228, + "amount": -3 + }, + { + "first": 221, + "second": 229, + "amount": -3 + }, + { + "first": 221, + "second": 230, + "amount": -4 + }, + { + "first": 221, + "second": 231, + "amount": -4 + }, + { + "first": 221, + "second": 232, + "amount": -3 + }, + { + "first": 221, + "second": 233, + "amount": -3 + }, + { + "first": 221, + "second": 234, + "amount": -3 + }, + { + "first": 221, + "second": 235, + "amount": -3 + }, + { + "first": 221, + "second": 236, + "amount": 3 + }, + { + "first": 221, + "second": 237, + "amount": 3 + }, + { + "first": 221, + "second": 238, + "amount": 3 + }, + { + "first": 221, + "second": 239, + "amount": 3 + }, + { + "first": 221, + "second": 240, + "amount": -3 + }, + { + "first": 221, + "second": 241, + "amount": -1 + }, + { + "first": 221, + "second": 242, + "amount": -3 + }, + { + "first": 221, + "second": 243, + "amount": -3 + }, + { + "first": 221, + "second": 244, + "amount": -3 + }, + { + "first": 221, + "second": 245, + "amount": -3 + }, + { + "first": 221, + "second": 246, + "amount": -3 + }, + { + "first": 221, + "second": 248, + "amount": -4 + }, + { + "first": 221, + "second": 249, + "amount": -1 + }, + { + "first": 221, + "second": 250, + "amount": -1 + }, + { + "first": 221, + "second": 251, + "amount": -1 + }, + { + "first": 221, + "second": 252, + "amount": -1 + }, + { + "first": 221, + "second": 253, + "amount": -2 + }, + { + "first": 221, + "second": 255, + "amount": -2 + }, + { + "first": 221, + "second": 338, + "amount": -3 + }, + { + "first": 221, + "second": 339, + "amount": -4 + }, + { + "first": 222, + "second": 65, + "amount": -2 + }, + { + "first": 222, + "second": 86, + "amount": -1 + }, + { + "first": 222, + "second": 89, + "amount": -1 + }, + { + "first": 222, + "second": 192, + "amount": -2 + }, + { + "first": 222, + "second": 193, + "amount": -2 + }, + { + "first": 222, + "second": 194, + "amount": -2 + }, + { + "first": 222, + "second": 195, + "amount": -2 + }, + { + "first": 222, + "second": 196, + "amount": -2 + }, + { + "first": 222, + "second": 197, + "amount": -2 + }, + { + "first": 222, + "second": 221, + "amount": -1 + }, + { + "first": 222, + "second": 376, + "amount": -1 + }, + { + "first": 223, + "second": 42, + "amount": -1 + }, + { + "first": 223, + "second": 48, + "amount": 0 + }, + { + "first": 223, + "second": 52, + "amount": 0 + }, + { + "first": 223, + "second": 55, + "amount": -2 + }, + { + "first": 223, + "second": 57, + "amount": -1 + }, + { + "first": 223, + "second": 64, + "amount": 0 + }, + { + "first": 223, + "second": 65, + "amount": 0 + }, + { + "first": 223, + "second": 67, + "amount": 0 + }, + { + "first": 223, + "second": 71, + "amount": 0 + }, + { + "first": 223, + "second": 79, + "amount": 0 + }, + { + "first": 223, + "second": 81, + "amount": 0 + }, + { + "first": 223, + "second": 84, + "amount": -3 + }, + { + "first": 223, + "second": 85, + "amount": 0 + }, + { + "first": 223, + "second": 86, + "amount": -4 + }, + { + "first": 223, + "second": 88, + "amount": -1 + }, + { + "first": 223, + "second": 89, + "amount": -4 + }, + { + "first": 223, + "second": 90, + "amount": 0 + }, + { + "first": 223, + "second": 97, + "amount": 0 + }, + { + "first": 223, + "second": 99, + "amount": 0 + }, + { + "first": 223, + "second": 100, + "amount": 0 + }, + { + "first": 223, + "second": 101, + "amount": 0 + }, + { + "first": 223, + "second": 102, + "amount": 0 + }, + { + "first": 223, + "second": 103, + "amount": 0 + }, + { + "first": 223, + "second": 111, + "amount": 0 + }, + { + "first": 223, + "second": 113, + "amount": 0 + }, + { + "first": 223, + "second": 116, + "amount": 0 + }, + { + "first": 223, + "second": 118, + "amount": 0 + }, + { + "first": 223, + "second": 119, + "amount": 0 + }, + { + "first": 223, + "second": 120, + "amount": 0 + }, + { + "first": 223, + "second": 121, + "amount": 0 + }, + { + "first": 223, + "second": 122, + "amount": 0 + }, + { + "first": 223, + "second": 169, + "amount": 0 + }, + { + "first": 223, + "second": 174, + "amount": 0 + }, + { + "first": 223, + "second": 180, + "amount": 0 + }, + { + "first": 223, + "second": 192, + "amount": 0 + }, + { + "first": 223, + "second": 193, + "amount": 0 + }, + { + "first": 223, + "second": 194, + "amount": 0 + }, + { + "first": 223, + "second": 195, + "amount": 0 + }, + { + "first": 223, + "second": 196, + "amount": 0 + }, + { + "first": 223, + "second": 197, + "amount": 0 + }, + { + "first": 223, + "second": 199, + "amount": 0 + }, + { + "first": 223, + "second": 210, + "amount": 0 + }, + { + "first": 223, + "second": 211, + "amount": 0 + }, + { + "first": 223, + "second": 212, + "amount": 0 + }, + { + "first": 223, + "second": 213, + "amount": 0 + }, + { + "first": 223, + "second": 214, + "amount": 0 + }, + { + "first": 223, + "second": 216, + "amount": 0 + }, + { + "first": 223, + "second": 217, + "amount": 0 + }, + { + "first": 223, + "second": 218, + "amount": 0 + }, + { + "first": 223, + "second": 219, + "amount": 0 + }, + { + "first": 223, + "second": 220, + "amount": 0 + }, + { + "first": 223, + "second": 221, + "amount": -4 + }, + { + "first": 223, + "second": 224, + "amount": 0 + }, + { + "first": 223, + "second": 225, + "amount": 0 + }, + { + "first": 223, + "second": 226, + "amount": 0 + }, + { + "first": 223, + "second": 227, + "amount": 0 + }, + { + "first": 223, + "second": 228, + "amount": 0 + }, + { + "first": 223, + "second": 229, + "amount": 0 + }, + { + "first": 223, + "second": 230, + "amount": 0 + }, + { + "first": 223, + "second": 231, + "amount": 0 + }, + { + "first": 223, + "second": 248, + "amount": 0 + }, + { + "first": 223, + "second": 338, + "amount": 0 + }, + { + "first": 223, + "second": 339, + "amount": 0 + }, + { + "first": 223, + "second": 376, + "amount": -4 + }, + { + "first": 224, + "second": 84, + "amount": -2 + }, + { + "first": 224, + "second": 86, + "amount": -3 + }, + { + "first": 224, + "second": 89, + "amount": -3 + }, + { + "first": 224, + "second": 118, + "amount": 0 + }, + { + "first": 224, + "second": 119, + "amount": 0 + }, + { + "first": 224, + "second": 121, + "amount": 0 + }, + { + "first": 224, + "second": 221, + "amount": -3 + }, + { + "first": 224, + "second": 376, + "amount": -3 + }, + { + "first": 225, + "second": 84, + "amount": -2 + }, + { + "first": 225, + "second": 86, + "amount": -3 + }, + { + "first": 225, + "second": 89, + "amount": -3 + }, + { + "first": 225, + "second": 118, + "amount": 0 + }, + { + "first": 225, + "second": 119, + "amount": 0 + }, + { + "first": 225, + "second": 121, + "amount": 0 + }, + { + "first": 225, + "second": 221, + "amount": -3 + }, + { + "first": 225, + "second": 376, + "amount": -3 + }, + { + "first": 226, + "second": 84, + "amount": -2 + }, + { + "first": 226, + "second": 86, + "amount": -3 + }, + { + "first": 226, + "second": 89, + "amount": -3 + }, + { + "first": 226, + "second": 118, + "amount": 0 + }, + { + "first": 226, + "second": 119, + "amount": 0 + }, + { + "first": 226, + "second": 121, + "amount": 0 + }, + { + "first": 226, + "second": 221, + "amount": -3 + }, + { + "first": 226, + "second": 376, + "amount": -3 + }, + { + "first": 227, + "second": 84, + "amount": -2 + }, + { + "first": 227, + "second": 86, + "amount": -3 + }, + { + "first": 227, + "second": 89, + "amount": -3 + }, + { + "first": 227, + "second": 118, + "amount": 0 + }, + { + "first": 227, + "second": 119, + "amount": 0 + }, + { + "first": 227, + "second": 121, + "amount": 0 + }, + { + "first": 227, + "second": 221, + "amount": -3 + }, + { + "first": 227, + "second": 376, + "amount": -3 + }, + { + "first": 228, + "second": 84, + "amount": -2 + }, + { + "first": 228, + "second": 86, + "amount": -3 + }, + { + "first": 228, + "second": 89, + "amount": -3 + }, + { + "first": 228, + "second": 118, + "amount": 0 + }, + { + "first": 228, + "second": 119, + "amount": 0 + }, + { + "first": 228, + "second": 121, + "amount": 0 + }, + { + "first": 228, + "second": 221, + "amount": -3 + }, + { + "first": 228, + "second": 376, + "amount": -3 + }, + { + "first": 229, + "second": 84, + "amount": -2 + }, + { + "first": 229, + "second": 86, + "amount": -3 + }, + { + "first": 229, + "second": 89, + "amount": -3 + }, + { + "first": 229, + "second": 118, + "amount": 0 + }, + { + "first": 229, + "second": 119, + "amount": 0 + }, + { + "first": 229, + "second": 121, + "amount": 0 + }, + { + "first": 229, + "second": 221, + "amount": -3 + }, + { + "first": 229, + "second": 376, + "amount": -3 + }, + { + "first": 230, + "second": 42, + "amount": -2 + }, + { + "first": 230, + "second": 47, + "amount": -2 + }, + { + "first": 230, + "second": 64, + "amount": 0 + }, + { + "first": 230, + "second": 67, + "amount": 0 + }, + { + "first": 230, + "second": 71, + "amount": 0 + }, + { + "first": 230, + "second": 79, + "amount": 0 + }, + { + "first": 230, + "second": 81, + "amount": 0 + }, + { + "first": 230, + "second": 84, + "amount": -4 + }, + { + "first": 230, + "second": 86, + "amount": -2 + }, + { + "first": 230, + "second": 87, + "amount": -1 + }, + { + "first": 230, + "second": 88, + "amount": -1 + }, + { + "first": 230, + "second": 89, + "amount": -2 + }, + { + "first": 230, + "second": 90, + "amount": 0 + }, + { + "first": 230, + "second": 92, + "amount": -1 + }, + { + "first": 230, + "second": 102, + "amount": 0 + }, + { + "first": 230, + "second": 115, + "amount": 0 + }, + { + "first": 230, + "second": 116, + "amount": 0 + }, + { + "first": 230, + "second": 118, + "amount": 0 + }, + { + "first": 230, + "second": 119, + "amount": 0 + }, + { + "first": 230, + "second": 120, + "amount": -1 + }, + { + "first": 230, + "second": 121, + "amount": 0 + }, + { + "first": 230, + "second": 122, + "amount": 0 + }, + { + "first": 230, + "second": 169, + "amount": 0 + }, + { + "first": 230, + "second": 174, + "amount": 0 + }, + { + "first": 230, + "second": 199, + "amount": 0 + }, + { + "first": 230, + "second": 210, + "amount": 0 + }, + { + "first": 230, + "second": 211, + "amount": 0 + }, + { + "first": 230, + "second": 212, + "amount": 0 + }, + { + "first": 230, + "second": 213, + "amount": 0 + }, + { + "first": 230, + "second": 214, + "amount": 0 + }, + { + "first": 230, + "second": 216, + "amount": 0 + }, + { + "first": 230, + "second": 221, + "amount": -2 + }, + { + "first": 230, + "second": 338, + "amount": 0 + }, + { + "first": 230, + "second": 376, + "amount": -2 + }, + { + "first": 231, + "second": 47, + "amount": -1 + }, + { + "first": 231, + "second": 64, + "amount": 0 + }, + { + "first": 231, + "second": 67, + "amount": 0 + }, + { + "first": 231, + "second": 71, + "amount": 0 + }, + { + "first": 231, + "second": 79, + "amount": 0 + }, + { + "first": 231, + "second": 81, + "amount": 0 + }, + { + "first": 231, + "second": 84, + "amount": -4 + }, + { + "first": 231, + "second": 85, + "amount": 0 + }, + { + "first": 231, + "second": 86, + "amount": -2 + }, + { + "first": 231, + "second": 87, + "amount": -1 + }, + { + "first": 231, + "second": 88, + "amount": -1 + }, + { + "first": 231, + "second": 89, + "amount": -2 + }, + { + "first": 231, + "second": 90, + "amount": 0 + }, + { + "first": 231, + "second": 92, + "amount": -1 + }, + { + "first": 231, + "second": 97, + "amount": 0 + }, + { + "first": 231, + "second": 99, + "amount": 0 + }, + { + "first": 231, + "second": 100, + "amount": 0 + }, + { + "first": 231, + "second": 101, + "amount": 0 + }, + { + "first": 231, + "second": 103, + "amount": 0 + }, + { + "first": 231, + "second": 111, + "amount": 0 + }, + { + "first": 231, + "second": 113, + "amount": 0 + }, + { + "first": 231, + "second": 115, + "amount": 0 + }, + { + "first": 231, + "second": 116, + "amount": 0 + }, + { + "first": 231, + "second": 117, + "amount": 0 + }, + { + "first": 231, + "second": 118, + "amount": 0 + }, + { + "first": 231, + "second": 119, + "amount": 0 + }, + { + "first": 231, + "second": 120, + "amount": -1 + }, + { + "first": 231, + "second": 121, + "amount": 0 + }, + { + "first": 231, + "second": 122, + "amount": 0 + }, + { + "first": 231, + "second": 169, + "amount": 0 + }, + { + "first": 231, + "second": 171, + "amount": 0 + }, + { + "first": 231, + "second": 174, + "amount": 0 + }, + { + "first": 231, + "second": 180, + "amount": 0 + }, + { + "first": 231, + "second": 187, + "amount": 0 + }, + { + "first": 231, + "second": 199, + "amount": 0 + }, + { + "first": 231, + "second": 210, + "amount": 0 + }, + { + "first": 231, + "second": 211, + "amount": 0 + }, + { + "first": 231, + "second": 212, + "amount": 0 + }, + { + "first": 231, + "second": 213, + "amount": 0 + }, + { + "first": 231, + "second": 214, + "amount": 0 + }, + { + "first": 231, + "second": 216, + "amount": 0 + }, + { + "first": 231, + "second": 217, + "amount": 0 + }, + { + "first": 231, + "second": 218, + "amount": 0 + }, + { + "first": 231, + "second": 219, + "amount": 0 + }, + { + "first": 231, + "second": 220, + "amount": 0 + }, + { + "first": 231, + "second": 221, + "amount": -2 + }, + { + "first": 231, + "second": 224, + "amount": 0 + }, + { + "first": 231, + "second": 225, + "amount": 0 + }, + { + "first": 231, + "second": 226, + "amount": 0 + }, + { + "first": 231, + "second": 227, + "amount": 0 + }, + { + "first": 231, + "second": 228, + "amount": 0 + }, + { + "first": 231, + "second": 229, + "amount": 0 + }, + { + "first": 231, + "second": 230, + "amount": 0 + }, + { + "first": 231, + "second": 231, + "amount": 0 + }, + { + "first": 231, + "second": 248, + "amount": 0 + }, + { + "first": 231, + "second": 249, + "amount": 0 + }, + { + "first": 231, + "second": 250, + "amount": 0 + }, + { + "first": 231, + "second": 251, + "amount": 0 + }, + { + "first": 231, + "second": 252, + "amount": 0 + }, + { + "first": 231, + "second": 338, + "amount": 0 + }, + { + "first": 231, + "second": 339, + "amount": 0 + }, + { + "first": 231, + "second": 376, + "amount": -2 + }, + { + "first": 232, + "second": 42, + "amount": -2 + }, + { + "first": 232, + "second": 47, + "amount": -2 + }, + { + "first": 232, + "second": 64, + "amount": 0 + }, + { + "first": 232, + "second": 67, + "amount": 0 + }, + { + "first": 232, + "second": 71, + "amount": 0 + }, + { + "first": 232, + "second": 79, + "amount": 0 + }, + { + "first": 232, + "second": 81, + "amount": 0 + }, + { + "first": 232, + "second": 84, + "amount": -4 + }, + { + "first": 232, + "second": 86, + "amount": -2 + }, + { + "first": 232, + "second": 87, + "amount": -1 + }, + { + "first": 232, + "second": 88, + "amount": -1 + }, + { + "first": 232, + "second": 89, + "amount": -2 + }, + { + "first": 232, + "second": 90, + "amount": 0 + }, + { + "first": 232, + "second": 92, + "amount": -1 + }, + { + "first": 232, + "second": 102, + "amount": 0 + }, + { + "first": 232, + "second": 115, + "amount": 0 + }, + { + "first": 232, + "second": 116, + "amount": 0 + }, + { + "first": 232, + "second": 118, + "amount": 0 + }, + { + "first": 232, + "second": 119, + "amount": 0 + }, + { + "first": 232, + "second": 120, + "amount": -1 + }, + { + "first": 232, + "second": 121, + "amount": 0 + }, + { + "first": 232, + "second": 122, + "amount": 0 + }, + { + "first": 232, + "second": 169, + "amount": 0 + }, + { + "first": 232, + "second": 174, + "amount": 0 + }, + { + "first": 232, + "second": 199, + "amount": 0 + }, + { + "first": 232, + "second": 210, + "amount": 0 + }, + { + "first": 232, + "second": 211, + "amount": 0 + }, + { + "first": 232, + "second": 212, + "amount": 0 + }, + { + "first": 232, + "second": 213, + "amount": 0 + }, + { + "first": 232, + "second": 214, + "amount": 0 + }, + { + "first": 232, + "second": 216, + "amount": 0 + }, + { + "first": 232, + "second": 221, + "amount": -2 + }, + { + "first": 232, + "second": 338, + "amount": 0 + }, + { + "first": 232, + "second": 376, + "amount": -2 + }, + { + "first": 233, + "second": 42, + "amount": -2 + }, + { + "first": 233, + "second": 47, + "amount": -2 + }, + { + "first": 233, + "second": 64, + "amount": 0 + }, + { + "first": 233, + "second": 67, + "amount": 0 + }, + { + "first": 233, + "second": 71, + "amount": 0 + }, + { + "first": 233, + "second": 79, + "amount": 0 + }, + { + "first": 233, + "second": 81, + "amount": 0 + }, + { + "first": 233, + "second": 84, + "amount": -4 + }, + { + "first": 233, + "second": 86, + "amount": -2 + }, + { + "first": 233, + "second": 87, + "amount": -1 + }, + { + "first": 233, + "second": 88, + "amount": -1 + }, + { + "first": 233, + "second": 89, + "amount": -2 + }, + { + "first": 233, + "second": 90, + "amount": 0 + }, + { + "first": 233, + "second": 92, + "amount": -1 + }, + { + "first": 233, + "second": 102, + "amount": 0 + }, + { + "first": 233, + "second": 115, + "amount": 0 + }, + { + "first": 233, + "second": 116, + "amount": 0 + }, + { + "first": 233, + "second": 118, + "amount": 0 + }, + { + "first": 233, + "second": 119, + "amount": 0 + }, + { + "first": 233, + "second": 120, + "amount": -1 + }, + { + "first": 233, + "second": 121, + "amount": 0 + }, + { + "first": 233, + "second": 122, + "amount": 0 + }, + { + "first": 233, + "second": 169, + "amount": 0 + }, + { + "first": 233, + "second": 174, + "amount": 0 + }, + { + "first": 233, + "second": 199, + "amount": 0 + }, + { + "first": 233, + "second": 210, + "amount": 0 + }, + { + "first": 233, + "second": 211, + "amount": 0 + }, + { + "first": 233, + "second": 212, + "amount": 0 + }, + { + "first": 233, + "second": 213, + "amount": 0 + }, + { + "first": 233, + "second": 214, + "amount": 0 + }, + { + "first": 233, + "second": 216, + "amount": 0 + }, + { + "first": 233, + "second": 221, + "amount": -2 + }, + { + "first": 233, + "second": 338, + "amount": 0 + }, + { + "first": 233, + "second": 376, + "amount": -2 + }, + { + "first": 234, + "second": 42, + "amount": -2 + }, + { + "first": 234, + "second": 47, + "amount": -2 + }, + { + "first": 234, + "second": 64, + "amount": 0 + }, + { + "first": 234, + "second": 67, + "amount": 0 + }, + { + "first": 234, + "second": 71, + "amount": 0 + }, + { + "first": 234, + "second": 79, + "amount": 0 + }, + { + "first": 234, + "second": 81, + "amount": 0 + }, + { + "first": 234, + "second": 84, + "amount": -4 + }, + { + "first": 234, + "second": 86, + "amount": -2 + }, + { + "first": 234, + "second": 87, + "amount": -1 + }, + { + "first": 234, + "second": 88, + "amount": -1 + }, + { + "first": 234, + "second": 89, + "amount": -2 + }, + { + "first": 234, + "second": 90, + "amount": 0 + }, + { + "first": 234, + "second": 92, + "amount": -1 + }, + { + "first": 234, + "second": 102, + "amount": 0 + }, + { + "first": 234, + "second": 115, + "amount": 0 + }, + { + "first": 234, + "second": 116, + "amount": 0 + }, + { + "first": 234, + "second": 118, + "amount": 0 + }, + { + "first": 234, + "second": 119, + "amount": 0 + }, + { + "first": 234, + "second": 120, + "amount": -1 + }, + { + "first": 234, + "second": 121, + "amount": 0 + }, + { + "first": 234, + "second": 122, + "amount": 0 + }, + { + "first": 234, + "second": 169, + "amount": 0 + }, + { + "first": 234, + "second": 174, + "amount": 0 + }, + { + "first": 234, + "second": 199, + "amount": 0 + }, + { + "first": 234, + "second": 210, + "amount": 0 + }, + { + "first": 234, + "second": 211, + "amount": 0 + }, + { + "first": 234, + "second": 212, + "amount": 0 + }, + { + "first": 234, + "second": 213, + "amount": 0 + }, + { + "first": 234, + "second": 214, + "amount": 0 + }, + { + "first": 234, + "second": 216, + "amount": 0 + }, + { + "first": 234, + "second": 221, + "amount": -2 + }, + { + "first": 234, + "second": 338, + "amount": 0 + }, + { + "first": 234, + "second": 376, + "amount": -2 + }, + { + "first": 235, + "second": 42, + "amount": -2 + }, + { + "first": 235, + "second": 47, + "amount": -2 + }, + { + "first": 235, + "second": 64, + "amount": 0 + }, + { + "first": 235, + "second": 67, + "amount": 0 + }, + { + "first": 235, + "second": 71, + "amount": 0 + }, + { + "first": 235, + "second": 79, + "amount": 0 + }, + { + "first": 235, + "second": 81, + "amount": 0 + }, + { + "first": 235, + "second": 84, + "amount": -4 + }, + { + "first": 235, + "second": 86, + "amount": -2 + }, + { + "first": 235, + "second": 87, + "amount": -1 + }, + { + "first": 235, + "second": 88, + "amount": -1 + }, + { + "first": 235, + "second": 89, + "amount": -2 + }, + { + "first": 235, + "second": 90, + "amount": 0 + }, + { + "first": 235, + "second": 92, + "amount": -1 + }, + { + "first": 235, + "second": 102, + "amount": 0 + }, + { + "first": 235, + "second": 115, + "amount": 0 + }, + { + "first": 235, + "second": 116, + "amount": 0 + }, + { + "first": 235, + "second": 118, + "amount": 0 + }, + { + "first": 235, + "second": 119, + "amount": 0 + }, + { + "first": 235, + "second": 120, + "amount": -1 + }, + { + "first": 235, + "second": 121, + "amount": 0 + }, + { + "first": 235, + "second": 122, + "amount": 0 + }, + { + "first": 235, + "second": 169, + "amount": 0 + }, + { + "first": 235, + "second": 174, + "amount": 0 + }, + { + "first": 235, + "second": 199, + "amount": 0 + }, + { + "first": 235, + "second": 210, + "amount": 0 + }, + { + "first": 235, + "second": 211, + "amount": 0 + }, + { + "first": 235, + "second": 212, + "amount": 0 + }, + { + "first": 235, + "second": 213, + "amount": 0 + }, + { + "first": 235, + "second": 214, + "amount": 0 + }, + { + "first": 235, + "second": 216, + "amount": 0 + }, + { + "first": 235, + "second": 221, + "amount": -2 + }, + { + "first": 235, + "second": 338, + "amount": 0 + }, + { + "first": 235, + "second": 376, + "amount": -2 + }, + { + "first": 236, + "second": 84, + "amount": 3 + }, + { + "first": 236, + "second": 86, + "amount": 3 + }, + { + "first": 236, + "second": 89, + "amount": 3 + }, + { + "first": 236, + "second": 221, + "amount": 3 + }, + { + "first": 236, + "second": 376, + "amount": 3 + }, + { + "first": 237, + "second": 84, + "amount": 3 + }, + { + "first": 237, + "second": 86, + "amount": 3 + }, + { + "first": 237, + "second": 89, + "amount": 3 + }, + { + "first": 237, + "second": 221, + "amount": 3 + }, + { + "first": 237, + "second": 376, + "amount": 3 + }, + { + "first": 238, + "second": 84, + "amount": 3 + }, + { + "first": 238, + "second": 86, + "amount": 3 + }, + { + "first": 238, + "second": 89, + "amount": 3 + }, + { + "first": 238, + "second": 221, + "amount": 3 + }, + { + "first": 238, + "second": 376, + "amount": 3 + }, + { + "first": 239, + "second": 84, + "amount": 3 + }, + { + "first": 239, + "second": 86, + "amount": 3 + }, + { + "first": 239, + "second": 89, + "amount": 3 + }, + { + "first": 239, + "second": 221, + "amount": 3 + }, + { + "first": 239, + "second": 376, + "amount": 3 + }, + { + "first": 240, + "second": 65, + "amount": 0 + }, + { + "first": 240, + "second": 84, + "amount": -3 + }, + { + "first": 240, + "second": 86, + "amount": -3 + }, + { + "first": 240, + "second": 87, + "amount": 0 + }, + { + "first": 240, + "second": 88, + "amount": -1 + }, + { + "first": 240, + "second": 89, + "amount": -3 + }, + { + "first": 240, + "second": 102, + "amount": 0 + }, + { + "first": 240, + "second": 116, + "amount": 0 + }, + { + "first": 240, + "second": 119, + "amount": 0 + }, + { + "first": 240, + "second": 120, + "amount": -1 + }, + { + "first": 240, + "second": 192, + "amount": 0 + }, + { + "first": 240, + "second": 193, + "amount": 0 + }, + { + "first": 240, + "second": 194, + "amount": 0 + }, + { + "first": 240, + "second": 195, + "amount": 0 + }, + { + "first": 240, + "second": 196, + "amount": 0 + }, + { + "first": 240, + "second": 197, + "amount": 0 + }, + { + "first": 240, + "second": 221, + "amount": -3 + }, + { + "first": 240, + "second": 376, + "amount": -3 + }, + { + "first": 241, + "second": 84, + "amount": -2 + }, + { + "first": 241, + "second": 86, + "amount": -3 + }, + { + "first": 241, + "second": 89, + "amount": -3 + }, + { + "first": 241, + "second": 118, + "amount": 0 + }, + { + "first": 241, + "second": 119, + "amount": 0 + }, + { + "first": 241, + "second": 121, + "amount": 0 + }, + { + "first": 241, + "second": 221, + "amount": -3 + }, + { + "first": 241, + "second": 376, + "amount": -3 + }, + { + "first": 242, + "second": 65, + "amount": 0 + }, + { + "first": 242, + "second": 84, + "amount": -3 + }, + { + "first": 242, + "second": 86, + "amount": -3 + }, + { + "first": 242, + "second": 87, + "amount": 0 + }, + { + "first": 242, + "second": 88, + "amount": -1 + }, + { + "first": 242, + "second": 89, + "amount": -3 + }, + { + "first": 242, + "second": 102, + "amount": 0 + }, + { + "first": 242, + "second": 116, + "amount": 0 + }, + { + "first": 242, + "second": 119, + "amount": 0 + }, + { + "first": 242, + "second": 120, + "amount": -1 + }, + { + "first": 242, + "second": 192, + "amount": 0 + }, + { + "first": 242, + "second": 193, + "amount": 0 + }, + { + "first": 242, + "second": 194, + "amount": 0 + }, + { + "first": 242, + "second": 195, + "amount": 0 + }, + { + "first": 242, + "second": 196, + "amount": 0 + }, + { + "first": 242, + "second": 197, + "amount": 0 + }, + { + "first": 242, + "second": 221, + "amount": -3 + }, + { + "first": 242, + "second": 376, + "amount": -3 + }, + { + "first": 243, + "second": 65, + "amount": 0 + }, + { + "first": 243, + "second": 84, + "amount": -3 + }, + { + "first": 243, + "second": 86, + "amount": -3 + }, + { + "first": 243, + "second": 87, + "amount": 0 + }, + { + "first": 243, + "second": 88, + "amount": -1 + }, + { + "first": 243, + "second": 89, + "amount": -3 + }, + { + "first": 243, + "second": 102, + "amount": 0 + }, + { + "first": 243, + "second": 116, + "amount": 0 + }, + { + "first": 243, + "second": 119, + "amount": 0 + }, + { + "first": 243, + "second": 120, + "amount": -1 + }, + { + "first": 243, + "second": 192, + "amount": 0 + }, + { + "first": 243, + "second": 193, + "amount": 0 + }, + { + "first": 243, + "second": 194, + "amount": 0 + }, + { + "first": 243, + "second": 195, + "amount": 0 + }, + { + "first": 243, + "second": 196, + "amount": 0 + }, + { + "first": 243, + "second": 197, + "amount": 0 + }, + { + "first": 243, + "second": 221, + "amount": -3 + }, + { + "first": 243, + "second": 376, + "amount": -3 + }, + { + "first": 244, + "second": 65, + "amount": 0 + }, + { + "first": 244, + "second": 84, + "amount": -3 + }, + { + "first": 244, + "second": 86, + "amount": -3 + }, + { + "first": 244, + "second": 87, + "amount": 0 + }, + { + "first": 244, + "second": 88, + "amount": -1 + }, + { + "first": 244, + "second": 89, + "amount": -3 + }, + { + "first": 244, + "second": 102, + "amount": 0 + }, + { + "first": 244, + "second": 116, + "amount": 0 + }, + { + "first": 244, + "second": 119, + "amount": 0 + }, + { + "first": 244, + "second": 120, + "amount": -1 + }, + { + "first": 244, + "second": 192, + "amount": 0 + }, + { + "first": 244, + "second": 193, + "amount": 0 + }, + { + "first": 244, + "second": 194, + "amount": 0 + }, + { + "first": 244, + "second": 195, + "amount": 0 + }, + { + "first": 244, + "second": 196, + "amount": 0 + }, + { + "first": 244, + "second": 197, + "amount": 0 + }, + { + "first": 244, + "second": 221, + "amount": -3 + }, + { + "first": 244, + "second": 376, + "amount": -3 + }, + { + "first": 245, + "second": 65, + "amount": 0 + }, + { + "first": 245, + "second": 84, + "amount": -3 + }, + { + "first": 245, + "second": 86, + "amount": -3 + }, + { + "first": 245, + "second": 87, + "amount": 0 + }, + { + "first": 245, + "second": 88, + "amount": -1 + }, + { + "first": 245, + "second": 89, + "amount": -3 + }, + { + "first": 245, + "second": 102, + "amount": 0 + }, + { + "first": 245, + "second": 116, + "amount": 0 + }, + { + "first": 245, + "second": 119, + "amount": 0 + }, + { + "first": 245, + "second": 120, + "amount": -1 + }, + { + "first": 245, + "second": 192, + "amount": 0 + }, + { + "first": 245, + "second": 193, + "amount": 0 + }, + { + "first": 245, + "second": 194, + "amount": 0 + }, + { + "first": 245, + "second": 195, + "amount": 0 + }, + { + "first": 245, + "second": 196, + "amount": 0 + }, + { + "first": 245, + "second": 197, + "amount": 0 + }, + { + "first": 245, + "second": 221, + "amount": -3 + }, + { + "first": 245, + "second": 376, + "amount": -3 + }, + { + "first": 246, + "second": 65, + "amount": 0 + }, + { + "first": 246, + "second": 84, + "amount": -3 + }, + { + "first": 246, + "second": 86, + "amount": -3 + }, + { + "first": 246, + "second": 87, + "amount": 0 + }, + { + "first": 246, + "second": 88, + "amount": -1 + }, + { + "first": 246, + "second": 89, + "amount": -3 + }, + { + "first": 246, + "second": 102, + "amount": 0 + }, + { + "first": 246, + "second": 116, + "amount": 0 + }, + { + "first": 246, + "second": 119, + "amount": 0 + }, + { + "first": 246, + "second": 120, + "amount": -1 + }, + { + "first": 246, + "second": 192, + "amount": 0 + }, + { + "first": 246, + "second": 193, + "amount": 0 + }, + { + "first": 246, + "second": 194, + "amount": 0 + }, + { + "first": 246, + "second": 195, + "amount": 0 + }, + { + "first": 246, + "second": 196, + "amount": 0 + }, + { + "first": 246, + "second": 197, + "amount": 0 + }, + { + "first": 246, + "second": 221, + "amount": -3 + }, + { + "first": 246, + "second": 376, + "amount": -3 + }, + { + "first": 248, + "second": 42, + "amount": -2 + }, + { + "first": 248, + "second": 44, + "amount": -1 + }, + { + "first": 248, + "second": 46, + "amount": -1 + }, + { + "first": 248, + "second": 55, + "amount": -2 + }, + { + "first": 248, + "second": 64, + "amount": 0 + }, + { + "first": 248, + "second": 65, + "amount": 0 + }, + { + "first": 248, + "second": 67, + "amount": 0 + }, + { + "first": 248, + "second": 71, + "amount": 0 + }, + { + "first": 248, + "second": 79, + "amount": 0 + }, + { + "first": 248, + "second": 81, + "amount": 0 + }, + { + "first": 248, + "second": 83, + "amount": 0 + }, + { + "first": 248, + "second": 84, + "amount": -4 + }, + { + "first": 248, + "second": 86, + "amount": -4 + }, + { + "first": 248, + "second": 87, + "amount": -1 + }, + { + "first": 248, + "second": 88, + "amount": -2 + }, + { + "first": 248, + "second": 89, + "amount": -4 + }, + { + "first": 248, + "second": 90, + "amount": 0 + }, + { + "first": 248, + "second": 102, + "amount": 0 + }, + { + "first": 248, + "second": 115, + "amount": 0 + }, + { + "first": 248, + "second": 116, + "amount": 0 + }, + { + "first": 248, + "second": 118, + "amount": 0 + }, + { + "first": 248, + "second": 119, + "amount": 0 + }, + { + "first": 248, + "second": 120, + "amount": -1 + }, + { + "first": 248, + "second": 121, + "amount": 0 + }, + { + "first": 248, + "second": 122, + "amount": 0 + }, + { + "first": 248, + "second": 169, + "amount": 0 + }, + { + "first": 248, + "second": 174, + "amount": 0 + }, + { + "first": 248, + "second": 192, + "amount": 0 + }, + { + "first": 248, + "second": 193, + "amount": 0 + }, + { + "first": 248, + "second": 194, + "amount": 0 + }, + { + "first": 248, + "second": 195, + "amount": 0 + }, + { + "first": 248, + "second": 196, + "amount": 0 + }, + { + "first": 248, + "second": 197, + "amount": 0 + }, + { + "first": 248, + "second": 199, + "amount": 0 + }, + { + "first": 248, + "second": 210, + "amount": 0 + }, + { + "first": 248, + "second": 211, + "amount": 0 + }, + { + "first": 248, + "second": 212, + "amount": 0 + }, + { + "first": 248, + "second": 213, + "amount": 0 + }, + { + "first": 248, + "second": 214, + "amount": 0 + }, + { + "first": 248, + "second": 216, + "amount": 0 + }, + { + "first": 248, + "second": 221, + "amount": -4 + }, + { + "first": 248, + "second": 338, + "amount": 0 + }, + { + "first": 248, + "second": 376, + "amount": -4 + }, + { + "first": 249, + "second": 86, + "amount": -1 + }, + { + "first": 249, + "second": 89, + "amount": -1 + }, + { + "first": 249, + "second": 221, + "amount": -1 + }, + { + "first": 249, + "second": 376, + "amount": -1 + }, + { + "first": 250, + "second": 86, + "amount": -1 + }, + { + "first": 250, + "second": 89, + "amount": -1 + }, + { + "first": 250, + "second": 221, + "amount": -1 + }, + { + "first": 250, + "second": 376, + "amount": -1 + }, + { + "first": 251, + "second": 86, + "amount": -1 + }, + { + "first": 251, + "second": 89, + "amount": -1 + }, + { + "first": 251, + "second": 221, + "amount": -1 + }, + { + "first": 251, + "second": 376, + "amount": -1 + }, + { + "first": 252, + "second": 86, + "amount": -1 + }, + { + "first": 252, + "second": 89, + "amount": -1 + }, + { + "first": 252, + "second": 221, + "amount": -1 + }, + { + "first": 252, + "second": 376, + "amount": -1 + }, + { + "first": 253, + "second": 84, + "amount": -1 + }, + { + "first": 253, + "second": 86, + "amount": -2 + }, + { + "first": 253, + "second": 89, + "amount": -2 + }, + { + "first": 253, + "second": 97, + "amount": 0 + }, + { + "first": 253, + "second": 221, + "amount": -2 + }, + { + "first": 253, + "second": 230, + "amount": 0 + }, + { + "first": 253, + "second": 376, + "amount": -2 + }, + { + "first": 254, + "second": 42, + "amount": -2 + }, + { + "first": 254, + "second": 44, + "amount": -1 + }, + { + "first": 254, + "second": 46, + "amount": -1 + }, + { + "first": 254, + "second": 55, + "amount": -2 + }, + { + "first": 254, + "second": 64, + "amount": 0 + }, + { + "first": 254, + "second": 65, + "amount": 0 + }, + { + "first": 254, + "second": 67, + "amount": 0 + }, + { + "first": 254, + "second": 71, + "amount": 0 + }, + { + "first": 254, + "second": 79, + "amount": 0 + }, + { + "first": 254, + "second": 81, + "amount": 0 + }, + { + "first": 254, + "second": 83, + "amount": 0 + }, + { + "first": 254, + "second": 84, + "amount": -4 + }, + { + "first": 254, + "second": 86, + "amount": -4 + }, + { + "first": 254, + "second": 87, + "amount": -1 + }, + { + "first": 254, + "second": 88, + "amount": -2 + }, + { + "first": 254, + "second": 89, + "amount": -4 + }, + { + "first": 254, + "second": 90, + "amount": 0 + }, + { + "first": 254, + "second": 102, + "amount": 0 + }, + { + "first": 254, + "second": 115, + "amount": 0 + }, + { + "first": 254, + "second": 116, + "amount": 0 + }, + { + "first": 254, + "second": 118, + "amount": 0 + }, + { + "first": 254, + "second": 119, + "amount": 0 + }, + { + "first": 254, + "second": 120, + "amount": -1 + }, + { + "first": 254, + "second": 121, + "amount": 0 + }, + { + "first": 254, + "second": 122, + "amount": 0 + }, + { + "first": 254, + "second": 169, + "amount": 0 + }, + { + "first": 254, + "second": 174, + "amount": 0 + }, + { + "first": 254, + "second": 192, + "amount": 0 + }, + { + "first": 254, + "second": 193, + "amount": 0 + }, + { + "first": 254, + "second": 194, + "amount": 0 + }, + { + "first": 254, + "second": 195, + "amount": 0 + }, + { + "first": 254, + "second": 196, + "amount": 0 + }, + { + "first": 254, + "second": 197, + "amount": 0 + }, + { + "first": 254, + "second": 199, + "amount": 0 + }, + { + "first": 254, + "second": 210, + "amount": 0 + }, + { + "first": 254, + "second": 211, + "amount": 0 + }, + { + "first": 254, + "second": 212, + "amount": 0 + }, + { + "first": 254, + "second": 213, + "amount": 0 + }, + { + "first": 254, + "second": 214, + "amount": 0 + }, + { + "first": 254, + "second": 216, + "amount": 0 + }, + { + "first": 254, + "second": 221, + "amount": -4 + }, + { + "first": 254, + "second": 338, + "amount": 0 + }, + { + "first": 254, + "second": 376, + "amount": -4 + }, + { + "first": 255, + "second": 84, + "amount": -1 + }, + { + "first": 255, + "second": 86, + "amount": -2 + }, + { + "first": 255, + "second": 89, + "amount": -2 + }, + { + "first": 255, + "second": 97, + "amount": 0 + }, + { + "first": 255, + "second": 221, + "amount": -2 + }, + { + "first": 255, + "second": 230, + "amount": 0 + }, + { + "first": 255, + "second": 376, + "amount": -2 + }, + { + "first": 338, + "second": 38, + "amount": 0 + }, + { + "first": 338, + "second": 64, + "amount": -1 + }, + { + "first": 338, + "second": 67, + "amount": -1 + }, + { + "first": 338, + "second": 71, + "amount": -1 + }, + { + "first": 338, + "second": 74, + "amount": 0 + }, + { + "first": 338, + "second": 79, + "amount": -1 + }, + { + "first": 338, + "second": 81, + "amount": -1 + }, + { + "first": 338, + "second": 87, + "amount": 0 + }, + { + "first": 338, + "second": 97, + "amount": 0 + }, + { + "first": 338, + "second": 99, + "amount": 0 + }, + { + "first": 338, + "second": 100, + "amount": 0 + }, + { + "first": 338, + "second": 101, + "amount": 0 + }, + { + "first": 338, + "second": 102, + "amount": -1 + }, + { + "first": 338, + "second": 103, + "amount": 0 + }, + { + "first": 338, + "second": 106, + "amount": 1 + }, + { + "first": 338, + "second": 111, + "amount": 0 + }, + { + "first": 338, + "second": 113, + "amount": 0 + }, + { + "first": 338, + "second": 115, + "amount": 0 + }, + { + "first": 338, + "second": 116, + "amount": 0 + }, + { + "first": 338, + "second": 118, + "amount": -1 + }, + { + "first": 338, + "second": 119, + "amount": -1 + }, + { + "first": 338, + "second": 121, + "amount": -1 + }, + { + "first": 338, + "second": 169, + "amount": -1 + }, + { + "first": 338, + "second": 171, + "amount": -1 + }, + { + "first": 338, + "second": 174, + "amount": -1 + }, + { + "first": 338, + "second": 180, + "amount": 0 + }, + { + "first": 338, + "second": 199, + "amount": -1 + }, + { + "first": 338, + "second": 210, + "amount": -1 + }, + { + "first": 338, + "second": 211, + "amount": -1 + }, + { + "first": 338, + "second": 212, + "amount": -1 + }, + { + "first": 338, + "second": 213, + "amount": -1 + }, + { + "first": 338, + "second": 214, + "amount": -1 + }, + { + "first": 338, + "second": 216, + "amount": -1 + }, + { + "first": 338, + "second": 224, + "amount": 0 + }, + { + "first": 338, + "second": 225, + "amount": 0 + }, + { + "first": 338, + "second": 226, + "amount": 0 + }, + { + "first": 338, + "second": 227, + "amount": 0 + }, + { + "first": 338, + "second": 228, + "amount": 0 + }, + { + "first": 338, + "second": 229, + "amount": 0 + }, + { + "first": 338, + "second": 230, + "amount": 0 + }, + { + "first": 338, + "second": 231, + "amount": 0 + }, + { + "first": 338, + "second": 248, + "amount": 0 + }, + { + "first": 338, + "second": 338, + "amount": -1 + }, + { + "first": 338, + "second": 339, + "amount": 0 + }, + { + "first": 339, + "second": 42, + "amount": -2 + }, + { + "first": 339, + "second": 47, + "amount": -2 + }, + { + "first": 339, + "second": 64, + "amount": 0 + }, + { + "first": 339, + "second": 67, + "amount": 0 + }, + { + "first": 339, + "second": 71, + "amount": 0 + }, + { + "first": 339, + "second": 79, + "amount": 0 + }, + { + "first": 339, + "second": 81, + "amount": 0 + }, + { + "first": 339, + "second": 84, + "amount": -4 + }, + { + "first": 339, + "second": 86, + "amount": -2 + }, + { + "first": 339, + "second": 87, + "amount": -1 + }, + { + "first": 339, + "second": 88, + "amount": -1 + }, + { + "first": 339, + "second": 89, + "amount": -2 + }, + { + "first": 339, + "second": 90, + "amount": 0 + }, + { + "first": 339, + "second": 92, + "amount": -1 + }, + { + "first": 339, + "second": 102, + "amount": 0 + }, + { + "first": 339, + "second": 115, + "amount": 0 + }, + { + "first": 339, + "second": 116, + "amount": 0 + }, + { + "first": 339, + "second": 118, + "amount": 0 + }, + { + "first": 339, + "second": 119, + "amount": 0 + }, + { + "first": 339, + "second": 120, + "amount": -1 + }, + { + "first": 339, + "second": 121, + "amount": 0 + }, + { + "first": 339, + "second": 122, + "amount": 0 + }, + { + "first": 339, + "second": 169, + "amount": 0 + }, + { + "first": 339, + "second": 174, + "amount": 0 + }, + { + "first": 339, + "second": 199, + "amount": 0 + }, + { + "first": 339, + "second": 210, + "amount": 0 + }, + { + "first": 339, + "second": 211, + "amount": 0 + }, + { + "first": 339, + "second": 212, + "amount": 0 + }, + { + "first": 339, + "second": 213, + "amount": 0 + }, + { + "first": 339, + "second": 214, + "amount": 0 + }, + { + "first": 339, + "second": 216, + "amount": 0 + }, + { + "first": 339, + "second": 221, + "amount": -2 + }, + { + "first": 339, + "second": 338, + "amount": 0 + }, + { + "first": 339, + "second": 376, + "amount": -2 + }, + { + "first": 376, + "second": 32, + "amount": -2 + }, + { + "first": 376, + "second": 38, + "amount": -3 + }, + { + "first": 376, + "second": 41, + "amount": -1 + }, + { + "first": 376, + "second": 44, + "amount": -5 + }, + { + "first": 376, + "second": 45, + "amount": -1 + }, + { + "first": 376, + "second": 46, + "amount": -5 + }, + { + "first": 376, + "second": 47, + "amount": -6 + }, + { + "first": 376, + "second": 48, + "amount": -2 + }, + { + "first": 376, + "second": 50, + "amount": -1 + }, + { + "first": 376, + "second": 51, + "amount": -1 + }, + { + "first": 376, + "second": 52, + "amount": -4 + }, + { + "first": 376, + "second": 53, + "amount": -1 + }, + { + "first": 376, + "second": 54, + "amount": -3 + }, + { + "first": 376, + "second": 55, + "amount": 0 + }, + { + "first": 376, + "second": 56, + "amount": -3 + }, + { + "first": 376, + "second": 57, + "amount": -2 + }, + { + "first": 376, + "second": 58, + "amount": -4 + }, + { + "first": 376, + "second": 59, + "amount": -4 + }, + { + "first": 376, + "second": 64, + "amount": -3 + }, + { + "first": 376, + "second": 65, + "amount": -5 + }, + { + "first": 376, + "second": 66, + "amount": -1 + }, + { + "first": 376, + "second": 67, + "amount": -3 + }, + { + "first": 376, + "second": 68, + "amount": -1 + }, + { + "first": 376, + "second": 69, + "amount": -1 + }, + { + "first": 376, + "second": 70, + "amount": -1 + }, + { + "first": 376, + "second": 71, + "amount": -3 + }, + { + "first": 376, + "second": 72, + "amount": -1 + }, + { + "first": 376, + "second": 73, + "amount": -1 + }, + { + "first": 376, + "second": 74, + "amount": -6 + }, + { + "first": 376, + "second": 75, + "amount": -1 + }, + { + "first": 376, + "second": 76, + "amount": -1 + }, + { + "first": 376, + "second": 77, + "amount": -1 + }, + { + "first": 376, + "second": 78, + "amount": -1 + }, + { + "first": 376, + "second": 79, + "amount": -3 + }, + { + "first": 376, + "second": 80, + "amount": -1 + }, + { + "first": 376, + "second": 81, + "amount": -3 + }, + { + "first": 376, + "second": 82, + "amount": -1 + }, + { + "first": 376, + "second": 83, + "amount": -1 + }, + { + "first": 376, + "second": 88, + "amount": -1 + }, + { + "first": 376, + "second": 97, + "amount": -4 + }, + { + "first": 376, + "second": 99, + "amount": -4 + }, + { + "first": 376, + "second": 100, + "amount": -4 + }, + { + "first": 376, + "second": 101, + "amount": -4 + }, + { + "first": 376, + "second": 102, + "amount": -1 + }, + { + "first": 376, + "second": 103, + "amount": -4 + }, + { + "first": 376, + "second": 109, + "amount": -1 + }, + { + "first": 376, + "second": 110, + "amount": -1 + }, + { + "first": 376, + "second": 111, + "amount": -4 + }, + { + "first": 376, + "second": 112, + "amount": -1 + }, + { + "first": 376, + "second": 113, + "amount": -4 + }, + { + "first": 376, + "second": 114, + "amount": -1 + }, + { + "first": 376, + "second": 115, + "amount": -3 + }, + { + "first": 376, + "second": 117, + "amount": -1 + }, + { + "first": 376, + "second": 118, + "amount": -2 + }, + { + "first": 376, + "second": 119, + "amount": -1 + }, + { + "first": 376, + "second": 120, + "amount": -3 + }, + { + "first": 376, + "second": 121, + "amount": -2 + }, + { + "first": 376, + "second": 122, + "amount": -2 + }, + { + "first": 376, + "second": 169, + "amount": -3 + }, + { + "first": 376, + "second": 171, + "amount": -4 + }, + { + "first": 376, + "second": 174, + "amount": -3 + }, + { + "first": 376, + "second": 180, + "amount": -3 + }, + { + "first": 376, + "second": 181, + "amount": -1 + }, + { + "first": 376, + "second": 187, + "amount": -1 + }, + { + "first": 376, + "second": 192, + "amount": -5 + }, + { + "first": 376, + "second": 193, + "amount": -5 + }, + { + "first": 376, + "second": 194, + "amount": -5 + }, + { + "first": 376, + "second": 195, + "amount": -5 + }, + { + "first": 376, + "second": 196, + "amount": -5 + }, + { + "first": 376, + "second": 197, + "amount": -5 + }, + { + "first": 376, + "second": 198, + "amount": -5 + }, + { + "first": 376, + "second": 199, + "amount": -3 + }, + { + "first": 376, + "second": 200, + "amount": -1 + }, + { + "first": 376, + "second": 201, + "amount": -1 + }, + { + "first": 376, + "second": 202, + "amount": -1 + }, + { + "first": 376, + "second": 203, + "amount": -1 + }, + { + "first": 376, + "second": 204, + "amount": -1 + }, + { + "first": 376, + "second": 205, + "amount": -1 + }, + { + "first": 376, + "second": 206, + "amount": -1 + }, + { + "first": 376, + "second": 207, + "amount": -1 + }, + { + "first": 376, + "second": 209, + "amount": -1 + }, + { + "first": 376, + "second": 210, + "amount": -3 + }, + { + "first": 376, + "second": 211, + "amount": -3 + }, + { + "first": 376, + "second": 212, + "amount": -3 + }, + { + "first": 376, + "second": 213, + "amount": -3 + }, + { + "first": 376, + "second": 214, + "amount": -3 + }, + { + "first": 376, + "second": 216, + "amount": -3 + }, + { + "first": 376, + "second": 222, + "amount": -1 + }, + { + "first": 376, + "second": 224, + "amount": -3 + }, + { + "first": 376, + "second": 225, + "amount": -3 + }, + { + "first": 376, + "second": 226, + "amount": -3 + }, + { + "first": 376, + "second": 227, + "amount": -3 + }, + { + "first": 376, + "second": 228, + "amount": -3 + }, + { + "first": 376, + "second": 229, + "amount": -3 + }, + { + "first": 376, + "second": 230, + "amount": -4 + }, + { + "first": 376, + "second": 231, + "amount": -4 + }, + { + "first": 376, + "second": 232, + "amount": -3 + }, + { + "first": 376, + "second": 233, + "amount": -3 + }, + { + "first": 376, + "second": 234, + "amount": -3 + }, + { + "first": 376, + "second": 235, + "amount": -3 + }, + { + "first": 376, + "second": 236, + "amount": 3 + }, + { + "first": 376, + "second": 237, + "amount": 3 + }, + { + "first": 376, + "second": 238, + "amount": 3 + }, + { + "first": 376, + "second": 239, + "amount": 3 + }, + { + "first": 376, + "second": 240, + "amount": -3 + }, + { + "first": 376, + "second": 241, + "amount": -1 + }, + { + "first": 376, + "second": 242, + "amount": -3 + }, + { + "first": 376, + "second": 243, + "amount": -3 + }, + { + "first": 376, + "second": 244, + "amount": -3 + }, + { + "first": 376, + "second": 245, + "amount": -3 + }, + { + "first": 376, + "second": 246, + "amount": -3 + }, + { + "first": 376, + "second": 248, + "amount": -4 + }, + { + "first": 376, + "second": 249, + "amount": -1 + }, + { + "first": 376, + "second": 250, + "amount": -1 + }, + { + "first": 376, + "second": 251, + "amount": -1 + }, + { + "first": 376, + "second": 252, + "amount": -1 + }, + { + "first": 376, + "second": 253, + "amount": -2 + }, + { + "first": 376, + "second": 255, + "amount": -2 + }, + { + "first": 376, + "second": 338, + "amount": -3 + }, + { + "first": 376, + "second": 339, + "amount": -4 + } + ] +} diff --git a/src/client/render/gl/assets/msdf-atlas.png b/src/client/render/gl/assets/msdf-atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..d4987e31b83c5e336328dd71f8b3aae0ed94eaca GIT binary patch literal 522694 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


    iRh|&V{y*VfKW6H;mBqnB$w+=DB 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 void, +): GUI { + const gui = new GUI({ title: "Render Settings", width: 320 }); + gui.domElement.style.position = "fixed"; + gui.domElement.style.top = "8px"; + gui.domElement.style.right = "8px"; + gui.domElement.style.zIndex = "100"; + + makeDraggable(gui); + + const defaults = createRenderSettings(); + const props = walkTree(buildTree(settings, defaults), gui); + + wireActions(gui, settings, props, onSettingsChanged); + wireModifiedIndicators(gui, props, onSettingsChanged); + + gui.close(); + return gui; +} diff --git a/src/client/render/gl/debug/layout.ts b/src/client/render/gl/debug/layout.ts new file mode 100644 index 0000000000..9bfd49a812 --- /dev/null +++ b/src/client/render/gl/debug/layout.ts @@ -0,0 +1,747 @@ +import type { RenderSettings } from "../render-settings"; +import type { DebugNode } from "./folder"; +import { folder } from "./folder"; +import { color } from "./props/color"; +import { select } from "./props/select"; +import { slider } from "./props/slider"; +import { toggle } from "./props/toggle"; + +export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { + return [ + folder("Pass Enables", [ + toggle(s.passEnabled, "terrain", d.passEnabled), + toggle(s.passEnabled, "mapOverlay", d.passEnabled), + toggle(s.passEnabled, "structure", d.passEnabled), + toggle(s.passEnabled, "unit", d.passEnabled), + toggle(s.passEnabled, "name", d.passEnabled), + toggle(s.passEnabled, "falloutBloom", d.passEnabled), + toggle(s.passEnabled, "railroad", d.passEnabled), + toggle(s.passEnabled, "fx", d.passEnabled), + toggle(s.passEnabled, "bar", d.passEnabled), + toggle(s.passEnabled, "dayNight", d.passEnabled), + toggle(s.passEnabled, "nameDebug", d.passEnabled, "Name Debug Boxes"), + ]), + + folder("Fallout Bloom", [ + slider(s.falloutBloom, "broilSpeedCold", d.falloutBloom, 0, 0.05, 0.0001), + slider(s.falloutBloom, "broilSpeedHot", d.falloutBloom, 0, 0.05, 0.0001), + slider(s.falloutBloom, "noiseFreq1", d.falloutBloom, 0, 0.5, 0.001), + slider(s.falloutBloom, "noiseFreq2", d.falloutBloom, 0, 0.5, 0.001), + slider(s.falloutBloom, "contrastLoCold", d.falloutBloom, 0, 1, 0.01), + slider(s.falloutBloom, "contrastLoHot", d.falloutBloom, 0, 1, 0.01), + slider(s.falloutBloom, "contrastHiCold", d.falloutBloom, 0, 1, 0.01), + slider(s.falloutBloom, "contrastHiHot", d.falloutBloom, 0, 1, 0.01), + slider(s.falloutBloom, "metaFreq", d.falloutBloom, 0, 0.2, 0.001), + slider(s.falloutBloom, "intensityCold", d.falloutBloom, 0, 10, 0.05), + slider(s.falloutBloom, "intensityHot", d.falloutBloom, 0, 20, 0.1), + slider(s.falloutBloom, "metaInfluenceCold", d.falloutBloom, 0, 1, 0.01), + slider(s.falloutBloom, "metaInfluenceHot", d.falloutBloom, 0, 1, 0.01), + slider(s.falloutBloom, "opacityFadeEnd", d.falloutBloom, 0, 1, 0.01), + color( + s.falloutBloom, + "bloomR", + "bloomG", + "bloomB", + d.falloutBloom, + "Bloom Color", + ), + slider(s.falloutBloom, "bloomCoverage", d.falloutBloom, 0, 10, 0.1), + slider(s.falloutBloom, "heatDecayPerTick", d.falloutBloom, 0, 5, 0.01), + ]), + + folder("Day / Night", [ + select( + s.dayNight, + "mode", + d.dayNight, + ["light", "dark", "cycle"], + "Mode", + ), + slider(s.dayNight, "cycleTicks", d.dayNight, 60, 6000, 10), + slider( + s.dayNight, + "startPhase", + d.dayNight, + 0, + 1, + 0.01, + "Start Phase (0=noon)", + ), + slider(s.dayNight, "noonHold", d.dayNight, 0, 1, 0.01, "Noon Hold"), + slider(s.dayNight, "nightHold", d.dayNight, 0, 1, 0.01, "Night Hold"), + slider(s.dayNight, "nightAmbient", d.dayNight, 0, 1, 0.01), + slider(s.dayNight, "dayAmbient", d.dayNight, 0, 1, 0.01), + slider(s.dayNight, "falloffPower", d.dayNight, 0.5, 5, 0.1), + slider(s.dayNight, "falloutLightIntensity", d.dayNight, 0, 20, 0.1), + slider(s.dayNight, "falloutLightThreshold", d.dayNight, 0, 0.5, 0.001), + slider(s.dayNight, "blurZoomDivisor", d.dayNight, 1, 20, 0.5), + slider(s.dayNight, "lightRadiusMultiplier", d.dayNight, 0.1, 5, 0.1), + color( + s.dayNight, + "falloutLightR", + "falloutLightG", + "falloutLightB", + d.dayNight, + "Fallout Light Color", + ), + slider(s.dayNight, "emberLightIntensity", d.dayNight, 0, 20, 0.1), + color( + s.dayNight, + "emberLightR", + "emberLightG", + "emberLightB", + d.dayNight, + "Ember Light Color", + ), + ]), + + folder("Map Overlay", [ + slider(s.mapOverlay, "trailAlpha", d.mapOverlay, 0, 1, 0.01), + slider(s.mapOverlay, "defenseCheckerDarken", d.mapOverlay, 0, 1, 0.01), + slider(s.mapOverlay, "charcoalBase", d.mapOverlay, 0, 0.3, 0.005), + slider(s.mapOverlay, "charcoalVariation", d.mapOverlay, 0, 0.3, 0.005), + slider(s.mapOverlay, "charcoalAlpha", d.mapOverlay, 0, 1, 0.01), + slider( + s.mapOverlay, + "emberThresholdUnowned", + d.mapOverlay, + 0.5, + 1, + 0.005, + ), + slider(s.mapOverlay, "emberThresholdOwned", d.mapOverlay, 0.5, 1, 0.005), + slider(s.mapOverlay, "emberFlickerSpeed", d.mapOverlay, 0, 2, 0.01), + color( + s.mapOverlay, + "emberColorDarkR", + "emberColorDarkG", + "emberColorDarkB", + d.mapOverlay, + "Ember Color Dark", + ), + color( + s.mapOverlay, + "emberColorBrightR", + "emberColorBrightG", + "emberColorBrightB", + d.mapOverlay, + "Ember Color Bright", + ), + slider(s.mapOverlay, "emberStrengthUnowned", d.mapOverlay, 0, 2, 0.01), + slider( + s.mapOverlay, + "highlightBrighten", + d.mapOverlay, + 0, + 1, + 0.01, + "Highlight Brighten", + ), + slider( + s.mapOverlay, + "highlightThicken", + d.mapOverlay, + 0, + 10, + 1, + "Highlight Thicken (tiles)", + ), + + folder("Railroad", [ + slider(s.railroad, "railMinZoom", d.railroad, 0, 10, 0.1, "Min Zoom"), + slider( + s.railroad, + "railDetailZoom", + d.railroad, + 0, + 20, + 0.1, + "Detail Zoom", + ), + slider(s.railroad, "railAlpha", d.railroad, 0, 1, 0.01, "Alpha"), + ]), + ]), + + folder("Structure", [ + slider(s.structure, "iconSize", d.structure, 10, 60, 1), + slider(s.structure, "dotsZoomThreshold", d.structure, 0.1, 2, 0.05), + slider( + s.structure, + "iconScaleFactorZoomedOut", + d.structure, + 0.5, + 3, + 0.05, + ), + slider( + s.structure, + "highlightOutlineWidth", + d.structure, + 0, + 0.2, + 0.005, + "Highlight Outline W", + ), + slider( + s.structure, + "highlightDimAlpha", + d.structure, + 0, + 1, + 0.01, + "Highlight Dim Alpha", + ), + folder( + "Per-Shape", + Object.entries(s.structure.shapes).map(([name, cfg]) => + folder(name, [ + slider( + cfg, + "scale", + d.structure.shapes[name], + 0.5, + 2, + 0.05, + "Frame Scale", + ), + slider( + cfg, + "iconFill", + d.structure.shapes[name], + 0.2, + 1.5, + 0.05, + "Icon Fill", + ), + ]), + ), + ), + ]), + + folder("Bar", [ + slider(s.bar, "healthBarW", d.bar, 3, 30, 1, "Health Width"), + slider(s.bar, "healthBarH", d.bar, 1, 10, 1, "Health Height"), + slider(s.bar, "healthBarOffsetY", d.bar, -20, 0, 1, "Health Offset Y"), + slider(s.bar, "progressBarW", d.bar, 3, 30, 1, "Progress Width"), + slider(s.bar, "progressBarH", d.bar, 1, 10, 1, "Progress Height"), + slider(s.bar, "progressBarOffsetY", d.bar, 0, 20, 1, "Progress Offset Y"), + slider(s.bar, "borderWidth", d.bar, 0, 3, 0.5, "Border Width"), + slider(s.bar, "threshold1", d.bar, 0, 1, 0.05, "Red→Orange"), + slider(s.bar, "threshold2", d.bar, 0, 1, 0.05, "Orange→Yellow"), + slider(s.bar, "threshold3", d.bar, 0, 1, 0.05, "Yellow→Green"), + color(s.bar, "colorRedR", "colorRedG", "colorRedB", d.bar, "Red"), + color( + s.bar, + "colorOrangeR", + "colorOrangeG", + "colorOrangeB", + d.bar, + "Orange", + ), + color( + s.bar, + "colorYellowR", + "colorYellowG", + "colorYellowB", + d.bar, + "Yellow", + ), + color(s.bar, "colorGreenR", "colorGreenG", "colorGreenB", d.bar, "Green"), + ]), + + folder("Unit", [ + slider(s.unit, "unitSize", d.unit, 4, 64, 1), + slider(s.unit, "flickerSpeed", d.unit, 0, 2, 0.01), + color(s.unit, "angryR", "angryG", "angryB", d.unit, "Angry Color"), + ]), + + folder("Name", [ + slider(s.name, "lerpSpeed", d.name, 1, 30, 0.5), + slider(s.name, "cullThreshold", d.name, 0, 0.05, 0.001), + slider(s.name, "nameScaleFactor", d.name, 0.1, 1, 0.05), + slider(s.name, "nameScaleCap", d.name, 1, 10, 0.5), + slider(s.name, "troopSizeMultiplier", d.name, 0.1, 2, 0.05), + slider(s.name, "outlineWidth", d.name, 0, 10, 0.1, "Outline Width (px)"), + color( + s.name, + "outlineR", + "outlineG", + "outlineB", + d.name, + "Outline Color", + ), + toggle(s.name, "outlineUsePlayerColor", d.name, "Outline = Player Color"), + toggle(s.name, "fillUsePlayerColor", d.name, "Fill = Player Color"), + slider(s.name, "emojiRowOffset", d.name, 0, 5, 0.1, "Emoji Row Offset"), + slider(s.name, "statusRowOffset", d.name, 0, 5, 0.1, "Status Row Offset"), + ]), + + folder("FX", [ + slider(s.fx, "shockwaveRingWidth", d.fx, 0.01, 0.2, 0.005), + slider( + s.fx, + "nukeShockwaveDurationMs", + d.fx, + 200, + 5000, + 100, + "Nuke Shock Duration", + ), + slider( + s.fx, + "nukeShockwaveRadiusFactor", + d.fx, + 0.5, + 3, + 0.1, + "Nuke Shock Radius ×", + ), + slider( + s.fx, + "samShockwaveDurationMs", + d.fx, + 200, + 3000, + 50, + "SAM Shock Duration", + ), + slider(s.fx, "samShockwaveRadius", d.fx, 10, 100, 5, "SAM Shock Radius"), + slider( + s.fx, + "debrisLifetimeMs", + d.fx, + 1000, + 15000, + 500, + "Debris Lifetime", + ), + slider(s.fx, "debrisFadeIn", d.fx, 0, 0.5, 0.01, "Debris Fade In"), + slider(s.fx, "debrisFadeOut", d.fx, 0.3, 1, 0.01, "Debris Fade Out"), + slider( + s.fx, + "conquestLifetimeMs", + d.fx, + 500, + 8000, + 250, + "Conquest Lifetime", + ), + slider(s.fx, "conquestFadeIn", d.fx, 0, 0.5, 0.01, "Conquest Fade In"), + slider(s.fx, "conquestFadeOut", d.fx, 0.3, 1, 0.01, "Conquest Fade Out"), + ]), + + folder("Nuke Trajectory", [ + slider( + s.nukeTrajectory, + "lineWidth", + d.nukeTrajectory, + 0.5, + 5, + 0.25, + "Line Width (px)", + ), + slider( + s.nukeTrajectory, + "outlineWidth", + d.nukeTrajectory, + 0, + 4, + 0.25, + "Outline Width (px)", + ), + slider( + s.nukeTrajectory, + "dashTargetable", + d.nukeTrajectory, + 1, + 30, + 1, + "Dash (targetable)", + ), + slider( + s.nukeTrajectory, + "gapTargetable", + d.nukeTrajectory, + 1, + 20, + 1, + "Gap (targetable)", + ), + slider( + s.nukeTrajectory, + "dashUntargetable", + d.nukeTrajectory, + 1, + 20, + 1, + "Dash (untargetable)", + ), + slider( + s.nukeTrajectory, + "gapUntargetable", + d.nukeTrajectory, + 1, + 20, + 1, + "Gap (untargetable)", + ), + color( + s.nukeTrajectory, + "lineR", + "lineG", + "lineB", + d.nukeTrajectory, + "Line Color", + ), + color( + s.nukeTrajectory, + "interceptR", + "interceptG", + "interceptB", + d.nukeTrajectory, + "Intercept Color", + ), + color( + s.nukeTrajectory, + "outlineR", + "outlineG", + "outlineB", + d.nukeTrajectory, + "Outline Color", + ), + color( + s.nukeTrajectory, + "interceptOutlineR", + "interceptOutlineG", + "interceptOutlineB", + d.nukeTrajectory, + "Intercept Outline", + ), + slider( + s.nukeTrajectory, + "markerCircleRadius", + d.nukeTrajectory, + 2, + 16, + 1, + "Circle Marker (px)", + ), + slider( + s.nukeTrajectory, + "markerXRadius", + d.nukeTrajectory, + 2, + 16, + 1, + "X Marker (px)", + ), + ]), + + folder("Nuke Telegraph", [ + slider( + s.nukeTelegraph, + "strokeWidth", + d.nukeTelegraph, + 0.5, + 5, + 0.25, + "Stroke Width", + ), + slider( + s.nukeTelegraph, + "dashLen", + d.nukeTelegraph, + 2, + 30, + 1, + "Dash Length", + ), + slider( + s.nukeTelegraph, + "gapLen", + d.nukeTelegraph, + 1, + 20, + 1, + "Gap Length", + ), + slider( + s.nukeTelegraph, + "rotationSpeed", + d.nukeTelegraph, + 0, + 60, + 1, + "Rotation Speed", + ), + slider( + s.nukeTelegraph, + "baseAlpha", + d.nukeTelegraph, + 0, + 1, + 0.05, + "Base Alpha", + ), + slider( + s.nukeTelegraph, + "pulseAmplitude", + d.nukeTelegraph, + 0, + 0.5, + 0.01, + "Pulse Amplitude", + ), + slider( + s.nukeTelegraph, + "pulseSpeed", + d.nukeTelegraph, + 0, + 10, + 0.5, + "Pulse Speed", + ), + slider( + s.nukeTelegraph, + "fillAlphaOffset", + d.nukeTelegraph, + 0, + 1, + 0.05, + "Fill Alpha Offset", + ), + color( + s.nukeTelegraph, + "colorR", + "colorG", + "colorB", + d.nukeTelegraph, + "Color", + ), + ]), + + folder("Move Indicator", [ + slider( + s.moveIndicator, + "startRadius", + d.moveIndicator, + 1, + 40, + 1, + "Start Radius (px)", + ), + slider( + s.moveIndicator, + "chevronSize", + d.moveIndicator, + 1, + 20, + 0.5, + "Chevron Size (px)", + ), + slider( + s.moveIndicator, + "lineWidth", + d.moveIndicator, + 0.5, + 6, + 0.25, + "Line Width (px)", + ), + slider( + s.moveIndicator, + "duration", + d.moveIndicator, + 100, + 3000, + 50, + "Duration (ms)", + ), + slider( + s.moveIndicator, + "converge", + d.moveIndicator, + 0, + 1, + 0.05, + "Converge", + ), + ]), + + folder("SAM Radius", [ + slider( + s.samRadius, + "strokeWidth", + d.samRadius, + 0.5, + 5, + 0.1, + "Stroke Width", + ), + slider(s.samRadius, "dashLen", d.samRadius, 2, 30, 1, "Dash Length"), + slider(s.samRadius, "gapLen", d.samRadius, 1, 20, 1, "Gap Length"), + slider( + s.samRadius, + "rotationSpeed", + d.samRadius, + 0, + 40, + 1, + "Rotation Speed", + ), + slider(s.samRadius, "alpha", d.samRadius, 0, 1, 0.05, "Alpha"), + slider( + s.samRadius, + "outlineWidth", + d.samRadius, + 0, + 2, + 0.05, + "Outline Width", + ), + slider( + s.samRadius, + "outlineSoftness", + d.samRadius, + 0, + 1, + 0.05, + "Outline Softness", + ), + ]), + + folder("Bonus Popup", [ + slider(s.bonusPopup, "scale", d.bonusPopup, 1, 12, 0.5, "Scale"), + slider( + s.bonusPopup, + "lifetimeMs", + d.bonusPopup, + 500, + 5000, + 100, + "Lifetime (ms)", + ), + slider(s.bonusPopup, "riseSpeed", d.bonusPopup, 0, 10, 0.5, "Rise Speed"), + slider(s.bonusPopup, "yOffset", d.bonusPopup, -10, 10, 0.5, "Y Offset"), + slider( + s.bonusPopup, + "outlineWidth", + d.bonusPopup, + 0, + 5, + 0.1, + "Outline Width", + ), + color(s.bonusPopup, "colorR", "colorG", "colorB", d.bonusPopup, "Color"), + slider( + s.bonusPopup, + "minScreenScale", + d.bonusPopup, + 0, + 1, + 0.01, + "Min Screen Scale", + ), + slider(s.bonusPopup, "cullZoom", d.bonusPopup, 0, 2, 0.05, "Cull Zoom"), + ]), + + folder("Spawn Overlay", [ + slider( + s.spawnOverlay, + "highlightRadius", + d.spawnOverlay, + 1, + 20, + 1, + "Highlight Radius", + ), + slider( + s.spawnOverlay, + "highlightAlpha", + d.spawnOverlay, + 0, + 1, + 0.05, + "Highlight Alpha", + ), + slider( + s.spawnOverlay, + "selfMinRad", + d.spawnOverlay, + 1, + 30, + 0.5, + "Self Min Radius", + ), + slider( + s.spawnOverlay, + "selfMaxRad", + d.spawnOverlay, + 5, + 50, + 0.5, + "Self Max Radius", + ), + slider( + s.spawnOverlay, + "mateMinRad", + d.spawnOverlay, + 1, + 20, + 0.5, + "Mate Min Radius", + ), + slider( + s.spawnOverlay, + "mateMaxRad", + d.spawnOverlay, + 5, + 30, + 0.5, + "Mate Max Radius", + ), + slider( + s.spawnOverlay, + "animSpeed", + d.spawnOverlay, + 0.001, + 0.02, + 0.001, + "Anim Speed", + ), + slider( + s.spawnOverlay, + "gradientInnerEdge", + d.spawnOverlay, + 0.001, + 0.1, + 0.001, + "Gradient Inner Edge", + ), + slider( + s.spawnOverlay, + "gradientSolidEnd", + d.spawnOverlay, + 0.01, + 0.5, + 0.01, + "Gradient Solid End", + ), + ]), + + folder("Alt View", [ + slider(s.altView, "gridFontSize", d.altView, 6, 32, 1, "Grid Font Size"), + toggle(s.altView, "recolorStructures", d.altView, "Recolor Structures"), + ]), + + folder( + "Light Configs", + Object.entries(s.lightConfigs).map(([name, cfg]) => + folder(name, [ + slider(cfg, "radius", d.lightConfigs[name], 1, 60, 1), + slider(cfg, "intensity", d.lightConfigs[name], 0, 10, 0.1), + ]), + ), + ), + ]; +} diff --git a/src/client/render/gl/debug/props/color.ts b/src/client/render/gl/debug/props/color.ts new file mode 100644 index 0000000000..095072a225 --- /dev/null +++ b/src/client/render/gl/debug/props/color.ts @@ -0,0 +1,67 @@ +import type GUI from "lil-gui"; +import type { ColorController, Controller } from "lil-gui"; +import type { ConfigProp } from "../config-prop"; + +export function color>( + target: T, + rKey: keyof T & string, + gKey: keyof T & string, + bKey: keyof T & string, + defaults: T, + label?: string, +): ConfigProp { + const defaultR = defaults[rKey] as number; + const defaultG = defaults[gKey] as number; + const defaultB = defaults[bKey] as number; + + const proxy = { + color: { + r: target[rKey] as number, + g: target[gKey] as number, + b: target[bKey] as number, + }, + }; + let ctrl: Controller | undefined; + + return { + draw(folder: GUI) { + ctrl = folder + .addColor(proxy, "color") + .onChange((v: { r: number; g: number; b: number }) => { + (target as Record)[rKey] = v.r; + (target as Record)[gKey] = v.g; + (target as Record)[bKey] = v.b; + }); + if (label) ctrl.name(label); + return ctrl; + }, + isModified: () => + (target[rKey] as number) !== defaultR || + (target[gKey] as number) !== defaultG || + (target[bKey] as number) !== defaultB, + resetToDefault() { + (target as Record)[rKey] = defaultR; + (target as Record)[gKey] = defaultG; + (target as Record)[bKey] = defaultB; + proxy.color = { r: defaultR, g: defaultG, b: defaultB }; + (ctrl as ColorController | undefined)?.load( + "#" + + [defaultR, defaultG, defaultB] + .map((v) => + Math.round(v * 255) + .toString(16) + .padStart(2, "0"), + ) + .join(""), + ); + }, + updateDisplay() { + proxy.color = { + r: target[rKey] as number, + g: target[gKey] as number, + b: target[bKey] as number, + }; + ctrl?.updateDisplay(); + }, + }; +} diff --git a/src/client/render/gl/debug/props/select.ts b/src/client/render/gl/debug/props/select.ts new file mode 100644 index 0000000000..34ac0a8933 --- /dev/null +++ b/src/client/render/gl/debug/props/select.ts @@ -0,0 +1,29 @@ +import type GUI from "lil-gui"; +import type { Controller } from "lil-gui"; +import type { ConfigProp } from "../config-prop"; + +export function select>( + target: T, + key: keyof T & string, + defaults: T, + options: string[], + label?: string, +): ConfigProp { + const defaultVal = defaults[key] as string; + let ctrl: Controller | undefined; + return { + draw(folder: GUI) { + ctrl = folder.add(target, key, options); + if (label) ctrl.name(label); + return ctrl; + }, + isModified: () => (target[key] as string) !== defaultVal, + resetToDefault() { + (target as Record)[key] = defaultVal; + ctrl?.updateDisplay(); + }, + updateDisplay() { + ctrl?.updateDisplay(); + }, + }; +} diff --git a/src/client/render/gl/debug/props/slider.ts b/src/client/render/gl/debug/props/slider.ts new file mode 100644 index 0000000000..016591dc4e --- /dev/null +++ b/src/client/render/gl/debug/props/slider.ts @@ -0,0 +1,31 @@ +import type GUI from "lil-gui"; +import type { Controller } from "lil-gui"; +import type { ConfigProp } from "../config-prop"; + +export function slider>( + target: T, + key: keyof T & string, + defaults: T, + min: number, + max: number, + step: number, + label?: string, +): ConfigProp { + const defaultVal = defaults[key] as number; + let ctrl: Controller | undefined; + return { + draw(folder: GUI) { + ctrl = folder.add(target, key, min, max, step); + if (label) ctrl.name(label); + return ctrl; + }, + isModified: () => (target[key] as number) !== defaultVal, + resetToDefault() { + (target as Record)[key] = defaultVal; + ctrl?.updateDisplay(); + }, + updateDisplay() { + ctrl?.updateDisplay(); + }, + }; +} diff --git a/src/client/render/gl/debug/props/toggle.ts b/src/client/render/gl/debug/props/toggle.ts new file mode 100644 index 0000000000..0429fbefec --- /dev/null +++ b/src/client/render/gl/debug/props/toggle.ts @@ -0,0 +1,28 @@ +import type GUI from "lil-gui"; +import type { Controller } from "lil-gui"; +import type { ConfigProp } from "../config-prop"; + +export function toggle>( + target: T, + key: keyof T & string, + defaults: T, + label?: string, +): ConfigProp { + const defaultVal = defaults[key] as boolean; + let ctrl: Controller | undefined; + return { + draw(folder: GUI) { + ctrl = folder.add(target, key); + if (label) ctrl.name(label); + return ctrl; + }, + isModified: () => (target[key] as boolean) !== defaultVal, + resetToDefault() { + (target as Record)[key] = defaultVal; + ctrl?.updateDisplay(); + }, + updateDisplay() { + ctrl?.updateDisplay(); + }, + }; +} diff --git a/src/client/render/gl/debug/tree.ts b/src/client/render/gl/debug/tree.ts new file mode 100644 index 0000000000..e7caa69954 --- /dev/null +++ b/src/client/render/gl/debug/tree.ts @@ -0,0 +1,23 @@ +import GUI from "lil-gui"; +import type { ConfigProp } from "./config-prop"; +import type { DebugNode, FolderNode } from "./folder"; + +/** Walk the debug tree, drawing each node onto the GUI. Returns all leaf props. */ +export function walkTree(nodes: DebugNode[], parent: GUI): ConfigProp[] { + const props: ConfigProp[] = []; + for (const node of nodes) { + if (isFolderNode(node)) { + const sub = parent.addFolder(node.label); + props.push(...walkTree(node.children, sub)); + if (node.closed) sub.close(); + } else { + node.draw(parent); + props.push(node); + } + } + return props; +} + +function isFolderNode(node: DebugNode): node is FolderNode { + return (node as FolderNode).kind === "folder"; +} diff --git a/src/client/render/gl/debug/wiring.ts b/src/client/render/gl/debug/wiring.ts new file mode 100644 index 0000000000..1af7ef0aee --- /dev/null +++ b/src/client/render/gl/debug/wiring.ts @@ -0,0 +1,215 @@ +import GUI, { FunctionController } from "lil-gui"; +import type { RenderSettings } from "../render-settings"; +import { createRenderSettings, dumpSettings } from "../render-settings"; +import { deepAssign } from "../settings-utils"; +import type { ConfigProp } from "./config-prop"; + +// --------------------------------------------------------------------------- +// Draggable title bar +// --------------------------------------------------------------------------- + +export function makeDraggable(gui: GUI): void { + const titleBar = gui.domElement.querySelector( + ".title, .lil-title", + ) as HTMLElement | null; + if (!titleBar) return; + + titleBar.style.cursor = "grab"; + let dragging = false; + let didDrag = false; + let startX = 0, + startY = 0, + startLeft = 0, + startTop = 0; + + titleBar.addEventListener("mousedown", (e) => { + dragging = true; + didDrag = false; + titleBar.style.cursor = "grabbing"; + const rect = gui.domElement.getBoundingClientRect(); + startX = e.clientX; + startY = e.clientY; + startLeft = rect.left; + startTop = rect.top; + gui.domElement.style.left = rect.left + "px"; + gui.domElement.style.right = "auto"; + e.preventDefault(); + }); + + window.addEventListener("mousemove", (e) => { + if (!dragging) return; + didDrag = true; + gui.domElement.style.left = startLeft + e.clientX - startX + "px"; + gui.domElement.style.top = startTop + e.clientY - startY + "px"; + }); + + window.addEventListener("mouseup", () => { + if (!dragging) return; + dragging = false; + titleBar.style.cursor = "grab"; + }); + + titleBar.addEventListener( + "click", + (e) => { + if (didDrag) e.stopPropagation(); + }, + { capture: true }, + ); +} + +// --------------------------------------------------------------------------- +// Actions: Download JSON, Load JSON, Reset to Defaults +// --------------------------------------------------------------------------- + +export function wireActions( + gui: GUI, + settings: RenderSettings, + props: ConfigProp[], + onSettingsChanged?: () => void, +): void { + gui.add({ dump: () => dumpSettings(settings) }, "dump").name("Download JSON"); + + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = ".json"; + fileInput.style.display = "none"; + document.body.appendChild(fileInput); + + fileInput.addEventListener("change", () => { + const file = fileInput.files?.[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = () => { + try { + deepAssign(settings, JSON.parse(reader.result as string)); + props.forEach((p) => p.updateDisplay()); + onSettingsChanged?.(); + } catch (e) { + console.error("Failed to load render settings:", e); + } + }; + reader.readAsText(file); + fileInput.value = ""; + }); + + gui.add({ load: () => fileInput.click() }, "load").name("Load JSON"); + + gui + .add( + { + reset: () => { + deepAssign(settings, createRenderSettings()); + props.forEach((p) => p.resetToDefault()); + onSettingsChanged?.(); + }, + }, + "reset", + ) + .name("Reset to Defaults"); +} + +// --------------------------------------------------------------------------- +// Modified indicators: blue label + right-click reset context menu +// --------------------------------------------------------------------------- + +const MODIFIED_CLASS = "lil-modified"; + +let stylesInjected = false; +function injectModifiedStyles(): void { + if (stylesInjected) return; + stylesInjected = true; + const style = document.createElement("style"); + style.textContent = ` + .${MODIFIED_CLASS} .lil-name { color: #5ba8d6; } + .lil-reset-menu { + position: fixed; + z-index: 10000; + background: #1a1a2e; + border: 1px solid #444; + border-radius: 4px; + padding: 4px 0; + font: 12px sans-serif; + color: #ccc; + box-shadow: 0 2px 8px rgba(0,0,0,0.5); + } + .lil-reset-menu div { + padding: 4px 16px; + cursor: pointer; + white-space: nowrap; + } + .lil-reset-menu div:hover { + background: #2a2a4e; + color: #fff; + } + `; + document.head.appendChild(style); +} + +function createContextMenu(): HTMLDivElement { + const menu = document.createElement("div"); + menu.className = "lil-reset-menu"; + menu.style.display = "none"; + document.body.appendChild(menu); + document.addEventListener("mousedown", (e) => { + if (!menu.contains(e.target as Node)) menu.style.display = "none"; + }); + return menu; +} + +export function wireModifiedIndicators( + gui: GUI, + props: ConfigProp[], + onSettingsChanged?: () => void, +): void { + injectModifiedStyles(); + const contextMenu = createContextMenu(); + + // Map each lil-gui Controller back to its ConfigProp + const allControllers = gui.controllersRecursive(); + // Props were pushed in walk order, controllers are in the same order (minus FunctionControllers) + const propControllers = allControllers.filter( + (c) => !(c instanceof FunctionController), + ); + + propControllers.forEach((ctrl, i) => { + const prop = props[i]; + + const updateClass = () => + ctrl.domElement.classList.toggle(MODIFIED_CLASS, prop.isModified()); + + updateClass(); + + const prev = ctrl._onChange; + ctrl.onChange(function (...args: unknown[]) { + prev?.apply(ctrl, args as any); + updateClass(); + }); + + ctrl.$name.addEventListener("contextmenu", (e) => { + if (!prop.isModified()) return; + e.preventDefault(); + e.stopPropagation(); + + contextMenu.innerHTML = ""; + const item = document.createElement("div"); + item.textContent = "Reset to default"; + item.addEventListener("mousedown", (ev) => { + ev.stopPropagation(); + prop.resetToDefault(); + updateClass(); + onSettingsChanged?.(); + contextMenu.style.display = "none"; + }); + contextMenu.appendChild(item); + contextMenu.style.left = e.clientX + "px"; + contextMenu.style.top = e.clientY + "px"; + contextMenu.style.display = ""; + }); + }); + + // Wire onFinishChange for persistence + if (onSettingsChanged) { + allControllers.forEach((c) => c.onFinishChange(onSettingsChanged)); + } +} diff --git a/src/client/render/gl/dynamic-buffer.ts b/src/client/render/gl/dynamic-buffer.ts new file mode 100644 index 0000000000..86ce6f89fe --- /dev/null +++ b/src/client/render/gl/dynamic-buffer.ts @@ -0,0 +1,55 @@ +/** + * DynamicInstanceBuffer — manages grow-on-demand instance buffers. + * + * Encapsulates the pattern of doubling capacity when needed, allocating new + * Float32Array, copying old data, and rebinding the GL buffer. + */ + +export class DynamicInstanceBuffer { + private data: Float32Array; + private bytes: Uint8Array; + private capacity: number; + + constructor( + private gl: WebGL2RenderingContext, + private buf: WebGLBuffer, + initialCapacity: number, + private floatsPerInstance: number, + ) { + this.capacity = initialCapacity; + this.data = new Float32Array(initialCapacity * floatsPerInstance); + this.bytes = new Uint8Array(this.data.buffer); + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, this.data.byteLength, gl.DYNAMIC_DRAW); + } + + ensureCapacity(needed: number): void { + if (needed <= this.capacity) return; + while (this.capacity < needed) this.capacity *= 2; + const newData = new Float32Array(this.capacity * this.floatsPerInstance); + newData.set(this.data); + this.data = newData; + this.bytes = new Uint8Array(newData.buffer); + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.buf); + gl.bufferData(gl.ARRAY_BUFFER, this.data.byteLength, gl.DYNAMIC_DRAW); + } + + get float32(): Float32Array { + return this.data; + } + + get uint8(): Uint8Array { + return this.bytes; + } + + get buffer(): WebGLBuffer { + return this.buf; + } + + dispose(): void { + if (this.buf !== null && this.buf !== undefined) { + this.gl.deleteBuffer(this.buf); + } + } +} diff --git a/src/client/render/gl/events.ts b/src/client/render/gl/events.ts new file mode 100644 index 0000000000..a4a691105a --- /dev/null +++ b/src/client/render/gl/events.ts @@ -0,0 +1,96 @@ +import type { UnitState } from "../types"; + +/** Event data emitted by GameView for map interactions. */ +export interface MapPointerEvent { + /** CSS pixel X relative to viewport (clientX). */ + screenX: number; + /** CSS pixel Y relative to viewport (clientY). */ + screenY: number; + /** World-space X (fractional; floor for tile column). */ + worldX: number; + /** World-space Y (fractional; floor for tile row). */ + worldY: number; + /** Tile column (integer, -1 if out of bounds). */ + tileX: number; + /** Tile row (integer, -1 if out of bounds). */ + tileY: number; + /** Territory owner at this tile (0 = unowned/OOB). */ + ownerID: number; + /** Nearest mobile unit under cursor, or null. */ + unit: UnitState | null; + /** Nearest structure under cursor, or null. */ + structure: UnitState | null; + /** Mouse button: 0 = left, 1 = middle, 2 = right. */ + button: number; + /** Shift key held. */ + shiftKey: boolean; + /** Ctrl/Meta key held. */ + ctrlKey: boolean; + /** Alt key held. */ + altKey: boolean; +} + +/** Scroll event data emitted by GameView. */ +export interface MapScrollEvent { + deltaX: number; + deltaY: number; + shiftKey: boolean; + ctrlKey: boolean; + altKey: boolean; +} + +/** Alt-view temporarily peeked (space hold — enables altview + gridview). */ +export interface AltViewPeekEvent { + active: boolean; +} + +/** Grid-view default toggled (persistent resting state changed via 'M'). */ +export interface GridViewToggleEvent { + active: boolean; +} + +/** Map of event names to their payload types. */ +export interface GameViewEventMap { + /** Left-click (pointerdown + pointerup with < 10px movement). */ + click: MapPointerEvent; + /** Double-click. */ + dblclick: MapPointerEvent; + /** Middle-click (auxclick with button 1). */ + middleclick: MapPointerEvent; + /** Right-click / context menu. */ + contextmenu: MapPointerEvent; + /** Hovered entity changed (owner, unit, or structure differs from previous). */ + hover: MapPointerEvent; + /** Scroll with modifier keys (unmodified scroll is consumed by zoom). */ + scroll: MapScrollEvent; + /** User selected a radial menu item. */ + menuselect: RadialMenuSelectEvent; + /** Alt-view temporarily peeked (space hold — enables altview + gridview). */ + altviewpeek: AltViewPeekEvent; + /** Grid-view default toggled (M key). */ + gridviewtoggle: GridViewToggleEvent; +} + +/** A single item in the radial context menu. */ +export interface RadialMenuItem { + /** Unique identifier for this action. */ + id: string; + /** Emoji key into the atlas (e.g. "⚔️"), or empty string for no icon. */ + icon: string; + /** RGB color [0–1]. */ + color: [number, number, number]; + /** Whether this action is currently available. */ + enabled: boolean; + /** If present, clicking this item opens a submenu with these items. */ + subItems?: RadialMenuItem[]; +} + +/** Emitted when the user selects a radial menu item. */ +export interface RadialMenuSelectEvent { + /** Index of the selected segment. */ + index: number; + /** The item's id. */ + id: string; +} + +export type GameViewEventType = keyof GameViewEventMap; diff --git a/src/client/render/gl/game-view.ts b/src/client/render/gl/game-view.ts new file mode 100644 index 0000000000..05fc297678 --- /dev/null +++ b/src/client/render/gl/game-view.ts @@ -0,0 +1,410 @@ +/** + * GameView — public facade for the openfront-gl renderer. + * + * Wraps GPURenderer (rendering) and Camera (viewport math) as private + * implementation details. Handles all user interaction: drag-to-pan, + * wheel-to-zoom, click detection, hover tracking, and hit-testing. + * + * Consumers only touch GameView — they never import GPURenderer or Camera. + */ + +import type { + AttackRingInput, + BonusEvent, + ConquestFx, + DeadUnitFx, + GhostPreviewData, + NameEntry, + NukeTelegraphData, + NukeTrajectoryData, + PlayerState, + PlayerStatic, + PlayerStatusData, + RendererConfig, + TilePair, + UnitState, +} from "../types"; +import type { + GameViewEventMap, + GameViewEventType, + RadialMenuItem, +} from "./events"; +import type { MapKeyBindings } from "./map-interaction"; +import { MapInteraction } from "./map-interaction"; +import type { SpawnCenter } from "./passes/spawn-overlay-pass"; +import type { RenderSettings } from "./render-settings"; +import { GPURenderer } from "./renderer"; + +export class GameView { + private renderer: GPURenderer; + private resizeObs: ResizeObserver | null = null; + private interaction: MapInteraction; + + private listeners = new Map void>>(); + + constructor( + canvas: HTMLCanvasElement, + header: RendererConfig, + terrainBytes: Uint8Array, + paletteData: Float32Array, + raf?: typeof requestAnimationFrame, + caf?: typeof cancelAnimationFrame, + keyBindings?: MapKeyBindings, + ) { + this.renderer = new GPURenderer( + canvas, + header, + terrainBytes, + paletteData, + raf, + caf, + ); + + // Create interaction handler and wire DOM events + this.interaction = new MapInteraction({ + renderer: this.renderer, + emit: this.emit.bind(this), + raf: raf ?? requestAnimationFrame.bind(window), + caf: caf ?? cancelAnimationFrame.bind(window), + keyBindings, + }); + + canvas.addEventListener("pointerdown", (e) => + this.interaction.handlePointerDown(e), + ); + canvas.addEventListener("pointermove", (e) => + this.interaction.handlePointerMove(e), + ); + canvas.addEventListener("pointerup", (e) => + this.interaction.handlePointerUp(e), + ); + canvas.addEventListener("pointercancel", (e) => + this.interaction.handlePointerUp(e), + ); + canvas.addEventListener("wheel", (e) => this.interaction.handleWheel(e), { + passive: false, + }); + canvas.addEventListener("contextmenu", (e) => + this.interaction.handleContextMenu(e), + ); + canvas.addEventListener("dblclick", (e) => + this.interaction.handleDblClick(e), + ); + canvas.addEventListener("auxclick", (e) => + this.interaction.handleAuxClick(e), + ); + document.addEventListener("keydown", (e) => + this.interaction.handleKeyDown(e), + ); + document.addEventListener("keyup", (e) => this.interaction.handleKeyUp(e)); + + this.resizeObs = new ResizeObserver((entries) => { + for (const entry of entries) { + const { width, height } = entry.contentRect; + if (width > 0 && height > 0) this.renderer.resize(width, height); + } + }); + this.resizeObs.observe(canvas); + + const rect = canvas.getBoundingClientRect(); + if (rect.width > 0) this.renderer.resize(rect.width, rect.height); + } + + // ---- Event system ---- + + on( + event: K, + handler: (e: GameViewEventMap[K]) => void, + ): void { + let set = this.listeners.get(event); + if (!set) { + set = new Set(); + this.listeners.set(event, set); + } + set.add(handler as (e: unknown) => void); + } + + off( + event: K, + handler: (e: GameViewEventMap[K]) => void, + ): void { + this.listeners.get(event)?.delete(handler as (e: unknown) => void); + } + + private emit( + event: K, + data: GameViewEventMap[K], + ): void { + const set = this.listeners.get(event); + if (set) + for (const fn of set) (fn as (e: GameViewEventMap[K]) => void)(data); + } + + // ---- Radial menu ---- + + showRadialMenu( + screenX: number, + screenY: number, + items: RadialMenuItem[], + centerItem?: RadialMenuItem, + ): void { + this.renderer.showRadialMenu(screenX, screenY, items, centerItem); + // Cursor is at anchor — center starts hovered (synced with RadialMenuPass) + this.interaction.setMenuHoveredSeg( + this.renderer.radialMenuHitTest(screenX, screenY), + ); + } + + hideRadialMenu(): void { + this.renderer.hideRadialMenu(); + this.interaction.setMenuHoveredSeg(-1); + } + + openRadialSubMenu(subItems: RadialMenuItem[]): void { + this.renderer.openRadialSubMenu(subItems); + this.interaction.setMenuHoveredSeg(-1); + } + + goBackRadialMenu(): void { + this.renderer.goBackRadialMenu(); + this.interaction.setMenuHoveredSeg(-1); + } + + get radialMenuVisible(): boolean { + return this.renderer.radialMenuVisible; + } + registerRadialMenuIcons( + icons: { key: string; img: CanvasImageSource }[], + ): void { + this.renderer.registerRadialMenuIcons(icons); + } + + // ---- Camera ---- + + screenToWorld(screenX: number, screenY: number): { x: number; y: number } { + return this.renderer.screenToWorld(screenX, screenY); + } + + worldToScreen(worldX: number, worldY: number): { x: number; y: number } { + return this.renderer.worldToScreen(worldX, worldY); + } + + panTo(worldX: number, worldY: number): void { + this.renderer.panTo(worldX, worldY); + } + zoomTo(level: number): void { + this.renderer.zoomTo(level); + } + fitMap(): void { + this.renderer.fitMap(); + } + focusOwner(ownerID: number): void { + this.renderer.focusOwner(ownerID); + } + + focusBBox( + minX: number, + minY: number, + maxX: number, + maxY: number, + padding?: number, + ): void { + this.renderer.focusBBox(minX, minY, maxX, maxY, padding); + } + + getCameraState(): { x: number; y: number; z: number } { + return this.renderer.getCameraState(); + } + + setCameraState(x: number, y: number, z: number): void { + this.renderer.setCameraState(x, y, z); + } + + getOwnerAtWorld(worldX: number, worldY: number): number { + return this.renderer.getOwnerAtWorld(worldX, worldY); + } + + // ---- Data upload ---- + + applyFullFrame( + tileState: Uint16Array, + trailState: Uint8Array, + nukeEvents?: Array<{ tick: number; tiles: number[] }>, + currentTick?: number, + ): void { + this.renderer.applyFullFrame( + tileState, + trailState, + nukeEvents, + currentTick, + ); + } + + applyFullTiles(tileState: Uint16Array, trailState: Uint8Array): void { + this.renderer.applyFullTiles(tileState, trailState); + } + applyDelta(changedTiles: TilePair[], trailState: Uint8Array): void { + this.renderer.applyDelta(changedTiles, trailState); + } + uploadLiveDelta(tileState: Uint16Array, changedTiles: TilePair[]): void { + this.renderer.uploadLiveDelta(tileState, changedTiles); + } + uploadLiveTrailDelta( + trailState: Uint8Array, + dirtyRowMin: number, + dirtyRowMax: number, + ): void { + this.renderer.uploadLiveTrailDelta(trailState, dirtyRowMin, dirtyRowMax); + } + /** Upload full tile + trail state without resetting bloom (for live play). */ + uploadTileAndTrailState( + tileState: Uint16Array, + trailState: Uint8Array, + ): void { + this.renderer.uploadTileAndTrailState(tileState, trailState); + } + updatePalette(paletteData: Float32Array): void { + this.renderer.updatePalette(paletteData); + } + addPlayers(players: PlayerStatic[], paletteData: Float32Array): void { + this.renderer.addPlayers(players, paletteData); + } + uploadRailroadState(data: Uint8Array): void { + this.renderer.uploadRailroadState(data); + } + updateUnits(units: Map, gameTick: number): void { + this.renderer.updateUnits(units, gameTick); + } + updateNames( + names: Map, + players: Map, + snap: boolean, + statusData?: Map, + ): void { + this.renderer.updateNames(names, players, snap, statusData); + } + updateRelations(data: Uint8Array, size: number): void { + this.renderer.updateRelations(data, size); + } + updateStructures(units: Map): void { + this.renderer.updateStructures(units); + } + applyDeadUnits(deadUnits: DeadUnitFx[]): void { + this.renderer.applyDeadUnits(deadUnits); + } + applyConquestEvents(events: ConquestFx[]): void { + this.renderer.applyConquestEvents(events); + } + applyBonusEvents(events: BonusEvent[]): void { + this.renderer.applyBonusEvents(events); + } + applyRailroadDust(tileRefs: number[]): void { + this.renderer.applyRailroadDust(tileRefs); + } + updateAttackRings(rings: AttackRingInput[]): void { + this.renderer.updateAttackRings(rings); + } + clearFx(): void { + this.renderer.clearFx(); + } + setFxTimeFn(fn: () => number): void { + this.renderer.setFxTimeFn(fn); + } + + /** Update ghost structure preview (build-mode visualization). null = clear. */ + updateGhostPreview(data: GhostPreviewData | null): void { + this.interaction.setHasGhostPreview(data !== null); + this.renderer.updateGhostPreview(data); + } + + // ---- Nuke UI ---- + + /** Update nuke trajectory preview arc. null = hide. */ + updateNukeTrajectory(data: NukeTrajectoryData | null): void { + this.renderer.updateNukeTrajectory(data); + } + + /** Update in-flight nuke target telegraph circles. */ + updateNukeTelegraphs(data: NukeTelegraphData[]): void { + this.renderer.updateNukeTelegraphs(data); + } + + /** Update spawn phase overlay (tile highlights + breathing rings). */ + updateSpawnOverlay(inSpawnPhase: boolean, centers: SpawnCenter[]): void { + this.renderer.updateSpawnOverlay(inSpawnPhase, centers); + } + + // ---- Selection box ---- + + /** Show/hide the stippled selection box around a unit (warship selection). */ + setSelectedUnit(unitId: number | null): void { + this.renderer.setSelectedUnit(unitId); + } + + /** Flash converging-chevron animation at a warship move target. */ + showMoveIndicator(tileX: number, tileY: number, ownerID: number): void { + this.renderer.showMoveIndicator(tileX, tileY, ownerID); + } + + // ---- SAM radius (replay) ---- + + setSAMRadiusVisible(visible: boolean): void { + this.renderer.setSAMRadiusVisible(visible); + } + setSAMPerspective(playerID: number, allies: Set): void { + this.renderer.setSAMPerspective(playerID, allies); + } + setSAMColorMode(mode: "perspective" | "owner"): void { + this.renderer.setSAMColorMode(mode); + } + setSAMAllianceClusters(clusters: Map): void { + this.renderer.setSAMAllianceClusters(clusters); + } + + // ---- Other ---- + + setFitZoomOnDoubleClick(v: boolean): void { + this.interaction.fitZoomOnDoubleClick = v; + } + setDefaultGridView(v: boolean): void { + this.interaction.setDefaultGridView(v); + } + setLocalPlayerID(id: number): void { + this.renderer.setLocalPlayerID(id); + this.interaction.setLocalPlayerID(id); + } + setPanSpeed(speed: number): void { + this.interaction.setPanSpeed(speed); + } + setZoomSpeed(speed: number): void { + this.interaction.setZoomSpeed(speed); + } + setHighlightOwner(ownerID: number): void { + this.renderer.setHighlightOwner(ownerID); + } + setHighlightStructureTypes(unitTypes: string[] | null): void { + this.renderer.setHighlightStructureTypes(unitTypes); + } + getSettings(): RenderSettings { + return this.renderer.getSettings(); + } + get fps(): number { + return this.renderer.fps; + } + set onFrame(cb: ((ms: number) => void) | null) { + this.renderer.onFrame = cb; + } + set afterRender(cb: ((canvas: HTMLCanvasElement) => void) | null) { + this.renderer.afterRender = cb; + } + + // ---- Lifecycle ---- + + dispose(): void { + this.interaction.dispose(); + this.resizeObs?.disconnect(); + this.resizeObs = null; + this.listeners.clear(); + this.renderer.dispose(); + } +} diff --git a/src/client/render/gl/index.ts b/src/client/render/gl/index.ts new file mode 100644 index 0000000000..93e4ee05a0 --- /dev/null +++ b/src/client/render/gl/index.ts @@ -0,0 +1,30 @@ +export type { AttackRingInput } from "../types"; +export { createDebugGui } from "./debug/index"; +export type { + GameViewEventMap, + GameViewEventType, + MapPointerEvent, + MapScrollEvent, + RadialMenuItem, + RadialMenuSelectEvent, +} from "./events"; +export { GameView } from "./game-view"; +export { REPLAY_KEY_BINDINGS } from "./map-interaction"; +export type { MapKeyBindings } from "./map-interaction"; +export type { SpawnCenter } from "./passes/spawn-overlay-pass"; +export { createRenderSettings, dumpSettings } from "./render-settings"; +export type { RenderSettings } from "./render-settings"; +export { deepAssign, deepDiff } from "./settings-utils"; +export { buildTerrainRGBA, getPaletteSize } from "./utils/color-utils"; +export { buildNukeTrajectory, samRange } from "./utils/nuke-trajectory"; +export type { SAMInfo } from "./utils/nuke-trajectory"; + +// Re-export shared types used in the public API +export type { + NameEntry, + PlayerState, + PlayerStatic, + RendererConfig, + TilePair, + UnitState, +} from "../types"; diff --git a/src/client/render/gl/keyboard-pan.ts b/src/client/render/gl/keyboard-pan.ts new file mode 100644 index 0000000000..797e9e0f56 --- /dev/null +++ b/src/client/render/gl/keyboard-pan.ts @@ -0,0 +1,141 @@ +/** + * KeyboardPan — WASD camera panning, Q/E smooth zoom, and C fit-zoom. + * + * Tracks held keys and runs a requestAnimationFrame loop while any + * direction or zoom key is pressed. All movement is frame-rate + * independent. Pan speed is zoom-adaptive (faster when zoomed out). + * + * Skips all input when the user is typing in an input/textarea. + */ + +const WASD = new Set(["w", "a", "s", "d"]); +const ZOOM_KEYS = new Set(["q", "e"]); +const DEFAULT_PAN_SPEED = 800; // tiles per second at zoom = 1 +const DEFAULT_ZOOM_SPEED = 2.0; // zoom multiplier per second (e.g. 2× per second held) + +interface KeyboardPanDeps { + panBy(dx: number, dy: number): void; + zoomBy(factor: number): void; + focusOwner(ownerID: number): void; + fitMap(): void; + readonly zoom: number; +} + +export class KeyboardPan { + private deps: KeyboardPanDeps; + private raf: typeof requestAnimationFrame; + private caf: typeof cancelAnimationFrame; + + private held = new Set(); + private animId: number | null = null; + private lastTime = 0; + private localPlayerID = 0; + private panSpeed = DEFAULT_PAN_SPEED; + private zoomSpeed = DEFAULT_ZOOM_SPEED; + + constructor( + deps: KeyboardPanDeps, + raf: typeof requestAnimationFrame = requestAnimationFrame.bind(window), + caf: typeof cancelAnimationFrame = cancelAnimationFrame.bind(window), + ) { + this.deps = deps; + this.raf = raf; + this.caf = caf; + } + + handleKeyDown(e: KeyboardEvent): boolean { + if (isTyping()) return false; + + const key = e.key.toLowerCase(); + + if (key === "c" && !e.repeat) { + if (this.localPlayerID > 0) this.deps.focusOwner(this.localPlayerID); + else this.deps.fitMap(); + return true; + } + + if (WASD.has(key) || ZOOM_KEYS.has(key)) { + this.held.add(key); + if (this.animId === null) this.startLoop(); + return true; + } + + return false; + } + + handleKeyUp(e: KeyboardEvent): boolean { + const key = e.key.toLowerCase(); + if (WASD.has(key) || ZOOM_KEYS.has(key)) { + this.held.delete(key); + if (this.held.size === 0) this.stopLoop(); + return true; + } + return false; + } + + setLocalPlayerID(id: number): void { + this.localPlayerID = id; + } + setPanSpeed(speed: number): void { + this.panSpeed = speed; + } + setZoomSpeed(speed: number): void { + this.zoomSpeed = speed; + } + + dispose(): void { + this.stopLoop(); + this.held.clear(); + } + + // ---- Animation loop ---- + + private startLoop(): void { + this.lastTime = performance.now(); + this.animId = this.raf(this.loop); + } + + private stopLoop(): void { + if (this.animId !== null) { + this.caf(this.animId); + this.animId = null; + } + } + + private loop = (): void => { + const now = performance.now(); + const dt = Math.min((now - this.lastTime) / 1000, 0.1); // cap at 100ms + this.lastTime = now; + + const speed = this.panSpeed / this.deps.zoom; + let dx = 0; + let dy = 0; + if (this.held.has("a")) dx -= speed * dt; + if (this.held.has("d")) dx += speed * dt; + if (this.held.has("w")) dy -= speed * dt; + if (this.held.has("s")) dy += speed * dt; + + if (dx !== 0 || dy !== 0) this.deps.panBy(dx, dy); + + // Q/E smooth zoom: compute multiplicative factor for this frame + let zoomDir = 0; + if (this.held.has("e")) zoomDir += 1; + if (this.held.has("q")) zoomDir -= 1; + if (zoomDir !== 0) { + const factor = this.zoomSpeed ** (zoomDir * dt); + this.deps.zoomBy(factor); + } + + if (this.held.size > 0) this.animId = this.raf(this.loop); + else this.animId = null; + }; +} + +function isTyping(): boolean { + const el = document.activeElement; + if (!el) return false; + const tag = el.tagName; + if (tag === "INPUT" || tag === "TEXTAREA") return true; + if ((el as HTMLElement).isContentEditable) return true; + return false; +} diff --git a/src/client/render/gl/map-interaction.ts b/src/client/render/gl/map-interaction.ts new file mode 100644 index 0000000000..4e353206e5 --- /dev/null +++ b/src/client/render/gl/map-interaction.ts @@ -0,0 +1,418 @@ +/** + * MapInteraction — handles all DOM pointer and keyboard events for GameView. + * + * Owns: + * - Drag state: dragging, lastX/Y, downX/Y + * - Menu hover state: menuHoveredSeg + * - Timing guards: lastMenuDismissAt, lastGhostClickAt + * - Ghost preview flag: hasGhostPreview + * - Alt-view flag: altView (affiliation recoloring, configurable hold key) + * - Grid-view flag: gridView (coordinate grid, configurable toggle key) + * - Hover tracking: lastHoverOwner, lastHoverUnitId, lastHoverStructureId, lastHoverTileX/Y + * + * All handler methods (pointerdown, pointermove, pointerup, keydown, keyup, wheel, contextmenu, auxclick, dblclick) + * are defined here and bound by GameView. + */ + +import type { + GameViewEventMap, + GameViewEventType, + MapPointerEvent, +} from "./events"; +import { KeyboardPan } from "./keyboard-pan"; +import type { GPURenderer } from "./renderer"; + +const HIT_RADIUS_PX = 16; +const CLICK_THRESHOLD_SQ = 100; + +/** Describes a hold-key binding (key held = active, released = inactive). */ +export interface HoldKeyBinding { + /** KeyboardEvent.code to match (e.g. "Space", "KeyM"). */ + code: string; + /** Require shift modifier. Default false. */ + shift?: boolean; +} + +/** Describes a toggle-key binding (each press toggles). */ +export interface ToggleKeyBinding { + /** KeyboardEvent.key to match (e.g. "m", "g"). */ + key: string; +} + +/** Configurable keybindings for MapInteraction. */ +export interface MapKeyBindings { + /** Hold to peek alt-view (affiliation recoloring) + grid. */ + altViewPeek: HoldKeyBinding; + /** Toggle grid overlay on/off. */ + gridToggle: ToggleKeyBinding; +} + +/** Extension default: Space hold for altView peek, 'm' toggle for grid. */ +export const DEFAULT_KEY_BINDINGS: MapKeyBindings = { + altViewPeek: { code: "Space" }, + gridToggle: { key: "m" }, +}; + +/** Replay default: Shift+M hold for altView peek, 'm' toggle for grid. */ +export const REPLAY_KEY_BINDINGS: MapKeyBindings = { + altViewPeek: { code: "KeyG", shift: true }, + gridToggle: { key: "g" }, +}; + +interface InteractionDeps { + renderer: GPURenderer; + emit: ( + event: K, + payload: GameViewEventMap[K], + ) => void; + raf: typeof requestAnimationFrame; + caf: typeof cancelAnimationFrame; + keyBindings?: MapKeyBindings; +} + +export class MapInteraction { + private deps: InteractionDeps; + private keys: MapKeyBindings; + + // Drag state + private dragging = false; + private lastX = 0; + private lastY = 0; + private downX = 0; + private downY = 0; + + // Hover tracking + private lastHoverOwner = 0; + private lastHoverUnitId: number | null = null; + private lastHoverStructureId: number | null = null; + private lastHoverTileX = -1; + private lastHoverTileY = -1; + + // Timing guards + private hasGhostPreview = false; + private lastGhostClickAt = 0; + private lastMenuDismissAt = 0; + + // Menu hover + private menuHoveredSeg = -1; + + // Grid-view: coordinate grid overlay. Toggled by configured key, persisted. + private gridViewBase = false; + private gridView = false; + + // Alt-view: affiliation recoloring (no persistent toggle). + private altView = false; + + // Alt-view peek hold state. + private peekHeld = false; + + // Interaction settings (mutable — updated live by extension) + fitZoomOnDoubleClick = true; + + // Keyboard camera control (WASD pan + C fit-zoom) + private keyboardPan: KeyboardPan; + + constructor(deps: InteractionDeps) { + this.deps = deps; + this.keys = deps.keyBindings ?? DEFAULT_KEY_BINDINGS; + this.keyboardPan = new KeyboardPan(deps.renderer, deps.raf, deps.caf); + } + + // ---- Pointer event handlers ---- + + handlePointerDown(e: PointerEvent): void { + if (e.button !== 0) return; + + // If radial menu is open, clicking outside dismisses it + if (this.deps.renderer.radialMenuVisible) { + const hit = this.deps.renderer.radialMenuHitTest(e.clientX, e.clientY); + if (hit === -1) { + this.deps.renderer.hideRadialMenu(); + this.menuHoveredSeg = -1; + this.lastMenuDismissAt = performance.now(); + } + return; // consume the event either way — don't start dragging + } + + if (this.hasGhostPreview) this.lastGhostClickAt = performance.now(); + this.dragging = true; + this.lastX = e.clientX; + this.lastY = e.clientY; + this.downX = e.clientX; + this.downY = e.clientY; + (e.target as HTMLElement).setPointerCapture(e.pointerId); + } + + handlePointerMove(e: PointerEvent): void { + // Update radial menu hover + if (this.deps.renderer.radialMenuVisible) { + const hit = this.deps.renderer.radialMenuHitTest(e.clientX, e.clientY); + if (hit !== this.menuHoveredSeg) { + this.menuHoveredSeg = hit; + this.deps.renderer.setRadialMenuHover(hit); + } + return; // don't pan or update game hover while menu is open + } + + if (this.dragging) { + const dx = e.clientX - this.lastX; + const dy = e.clientY - this.lastY; + this.lastX = e.clientX; + this.lastY = e.clientY; + const dpr = window.devicePixelRatio || 1; + this.deps.renderer.panBy( + -(dx * dpr) / this.deps.renderer.zoom, + -(dy * dpr) / this.deps.renderer.zoom, + ); + return; + } + this.updateHover(e); + } + + handlePointerUp(e: PointerEvent): void { + if (e.button !== 0) return; + + // If radial menu is open, a click on a segment or center selects it. + // Don't hide the menu here — the menuselect handler decides whether to + // close or open a submenu. + if (this.deps.renderer.radialMenuVisible) { + if (this.menuHoveredSeg !== -1) { + const item = this.deps.renderer.getRadialMenuItemAt( + this.menuHoveredSeg, + ); + if (item && item.enabled) { + this.deps.emit("menuselect", { + index: this.menuHoveredSeg, + id: item.id, + }); + } + if (!this.deps.renderer.radialMenuVisible) { + this.lastMenuDismissAt = performance.now(); + } + this.menuHoveredSeg = -1; + } + return; + } + + if (!this.dragging) return; + this.dragging = false; + (e.target as HTMLElement).releasePointerCapture(e.pointerId); + const dx = e.clientX - this.downX; + const dy = e.clientY - this.downY; + if (dx * dx + dy * dy < CLICK_THRESHOLD_SQ) { + this.deps.emit("click", this.buildEvent(e, 0)); + } + } + + // ---- Keyboard event handlers ---- + + handleKeyDown(e: KeyboardEvent): void { + if (e.key === "Escape" && this.deps.renderer.radialMenuVisible) { + this.deps.renderer.hideRadialMenu(); + this.menuHoveredSeg = -1; + this.lastMenuDismissAt = performance.now(); + } + if ( + this.matchesHold(e, this.keys.altViewPeek) && + !e.repeat && + !this.peekHeld + ) { + e.preventDefault(); + this.peekHeld = true; + this.applyAltView(true); + this.applyGridView(true); + this.deps.emit("altviewpeek", { active: true }); + } + if (e.key === this.keys.gridToggle.key && !e.shiftKey && !e.repeat) { + this.gridViewBase = !this.gridViewBase; + this.applyGridView(this.gridViewBase); + this.deps.emit("gridviewtoggle", { active: this.gridViewBase }); + } + this.keyboardPan.handleKeyDown(e); + } + + handleKeyUp(e: KeyboardEvent): void { + if (e.code === this.keys.altViewPeek.code && this.peekHeld) { + e.preventDefault(); + this.peekHeld = false; + this.applyAltView(false); + this.applyGridView(this.gridViewBase); + this.deps.emit("altviewpeek", { active: false }); + } + this.keyboardPan.handleKeyUp(e); + } + + private matchesHold(e: KeyboardEvent, binding: HoldKeyBinding): boolean { + return e.code === binding.code && (!binding.shift || e.shiftKey); + } + + // ---- Other event handlers ---- + + handleWheel(e: WheelEvent): void { + e.preventDefault(); + if (e.shiftKey || e.ctrlKey || e.altKey) { + this.deps.emit("scroll", { + deltaX: e.deltaX, + deltaY: e.deltaY, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey, + altKey: e.altKey, + }); + return; + } + const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1; + this.deps.renderer.zoomAtScreen(factor, e.clientX, e.clientY); + } + + handleContextMenu(e: MouseEvent): void { + e.preventDefault(); + // Dismiss any open menu first — the external manager will decide whether to reopen + if (this.deps.renderer.radialMenuVisible) { + this.deps.renderer.hideRadialMenu(); + this.menuHoveredSeg = -1; + this.lastMenuDismissAt = performance.now(); + } + this.deps.emit("contextmenu", this.buildEvent(e, 2)); + } + + handleAuxClick(e: MouseEvent): void { + if (e.button !== 1) return; + e.preventDefault(); + this.deps.emit("middleclick", this.buildEvent(e, 1)); + } + + handleDblClick(e: MouseEvent): void { + // Suppress fitzoom if menu is open or was recently open + if (this.deps.renderer.radialMenuVisible) return; + const now = performance.now(); + if (now - this.lastMenuDismissAt < 500) return; + + const evt = this.buildEvent(e, 0); + if (this.fitZoomOnDoubleClick && now - this.lastGhostClickAt > 500) { + if (evt.ownerID !== 0) this.deps.renderer.focusOwner(evt.ownerID); + else this.deps.renderer.fitMap(); + } + this.deps.emit("dblclick", evt); + } + + // ---- Hover tracking ---- + + private updateHover(e: PointerEvent): void { + const world = this.deps.renderer.screenToWorld(e.clientX, e.clientY); + const tileX = Math.floor(world.x); + const tileY = Math.floor(world.y); + const ownerID = this.deps.renderer.getOwnerAtWorld(world.x, world.y); + const hitRadius = HIT_RADIUS_PX / this.deps.renderer.zoom; + const unit = this.deps.renderer.getUnitAtWorld(world.x, world.y, hitRadius); + const structure = this.deps.renderer.getStructureAtWorld( + world.x, + world.y, + hitRadius, + ); + const unitId = unit?.id ?? null; + const structureId = structure?.id ?? null; + + if ( + ownerID !== this.lastHoverOwner || + unitId !== this.lastHoverUnitId || + structureId !== this.lastHoverStructureId || + tileX !== this.lastHoverTileX || + tileY !== this.lastHoverTileY + ) { + this.lastHoverOwner = ownerID; + this.lastHoverUnitId = unitId; + this.lastHoverStructureId = structureId; + this.lastHoverTileX = tileX; + this.lastHoverTileY = tileY; + this.deps.renderer.setHighlightOwner(ownerID); + this.deps.emit("hover", { + screenX: e.clientX, + screenY: e.clientY, + worldX: world.x, + worldY: world.y, + tileX, + tileY, + ownerID, + unit, + structure, + button: 0, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey || e.metaKey, + altKey: e.altKey, + }); + } + } + + private buildEvent(e: MouseEvent, button: number): MapPointerEvent { + const world = this.deps.renderer.screenToWorld(e.clientX, e.clientY); + const hitRadius = HIT_RADIUS_PX / this.deps.renderer.zoom; + return { + screenX: e.clientX, + screenY: e.clientY, + worldX: world.x, + worldY: world.y, + tileX: Math.floor(world.x), + tileY: Math.floor(world.y), + ownerID: this.deps.renderer.getOwnerAtWorld(world.x, world.y), + unit: this.deps.renderer.getUnitAtWorld(world.x, world.y, hitRadius), + structure: this.deps.renderer.getStructureAtWorld( + world.x, + world.y, + hitRadius, + ), + button, + shiftKey: e.shiftKey, + ctrlKey: e.ctrlKey || e.metaKey, + altKey: e.altKey, + }; + } + + // ---- View helpers ---- + + private applyAltView(active: boolean): void { + if (active === this.altView) return; + this.altView = active; + this.deps.renderer.setAltView(active); + } + + private applyGridView(active: boolean): void { + if (active === this.gridView) return; + this.gridView = active; + this.deps.renderer.setGridView(active); + } + + // ---- Public API ---- + + setDefaultGridView(v: boolean): void { + this.gridViewBase = v; + if (!this.peekHeld) this.applyGridView(v); + } + + setHasGhostPreview(v: boolean): void { + this.hasGhostPreview = v; + } + + getMenuHoveredSeg(): number { + return this.menuHoveredSeg; + } + + setMenuHoveredSeg(v: number): void { + this.menuHoveredSeg = v; + } + + setLocalPlayerID(id: number): void { + this.keyboardPan.setLocalPlayerID(id); + } + + setPanSpeed(speed: number): void { + this.keyboardPan.setPanSpeed(speed); + } + + setZoomSpeed(speed: number): void { + this.keyboardPan.setZoomSpeed(speed); + } + + dispose(): void { + this.keyboardPan.dispose(); + } +} diff --git a/src/client/render/gl/passes/bar-pass.ts b/src/client/render/gl/passes/bar-pass.ts new file mode 100644 index 0000000000..8f14c8466c --- /dev/null +++ b/src/client/render/gl/passes/bar-pass.ts @@ -0,0 +1,262 @@ +/** + * BarPass — instanced health/progress bars above units and below structures. + * + * Two draw calls per frame: + * 1. Health bars (11x3 tiles, above warships) + * 2. Progress bars (14x3 tiles, below structures — construction + missile readiness) + * + * Data flow: + * UnitState.health / .missileTimerQueue / .constructionStartTick → CPU progress + * → instance VBO (x, y, progress) → GPU colored rectangle + */ + +import { + CONSTRUCTION_DURATIONS, + DELETION_MARK_DURATION, + missileReadiness, + WARSHIP_MAX_HEALTH, +} from "../../game-constants"; +import type { RendererConfig, UnitState } from "../../types"; +import { UT_MISSILE_SILO, UT_SAM_LAUNCHER } from "../../types"; +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; + +import barFragSrc from "../shaders/bar/bar.frag.glsl?raw"; +import barVertSrc from "../shaders/bar/bar.vert.glsl?raw"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const FLOATS_PER_INSTANCE = 3; // x, y, progress +const BYTES_PER_INSTANCE = FLOATS_PER_INSTANCE * 4; + +// --------------------------------------------------------------------------- +// BarPass +// --------------------------------------------------------------------------- + +export class BarPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + private maxBars = 2048; + + private uCamera: WebGLUniformLocation; + private uBarSize: WebGLUniformLocation; + private uBarOffset: WebGLUniformLocation; + private uBorderWidth: WebGLUniformLocation; + private uThresholds: WebGLUniformLocation; + private uColorRed: WebGLUniformLocation; + private uColorOrange: WebGLUniformLocation; + private uColorYellow: WebGLUniformLocation; + private uColorGreen: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private instanceBuf: WebGLBuffer; + + private healthData: Float32Array; + private healthCount = 0; + private progressData: Float32Array; + private progressCount = 0; + + private mapW: number; + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = header.mapWidth; + + // --- Shader program --- + this.program = createProgram(gl, barVertSrc, barFragSrc); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uBarSize = gl.getUniformLocation(this.program, "uBarSize")!; + this.uBarOffset = gl.getUniformLocation(this.program, "uBarOffset")!; + this.uBorderWidth = gl.getUniformLocation(this.program, "uBorderWidth")!; + this.uThresholds = gl.getUniformLocation(this.program, "uThresholds")!; + this.uColorRed = gl.getUniformLocation(this.program, "uColorRed")!; + this.uColorOrange = gl.getUniformLocation(this.program, "uColorOrange")!; + this.uColorYellow = gl.getUniformLocation(this.program, "uColorYellow")!; + this.uColorGreen = gl.getUniformLocation(this.program, "uColorGreen")!; + + // --- Instance data buffers (CPU-side) --- + this.healthData = new Float32Array(this.maxBars * FLOATS_PER_INSTANCE); + this.progressData = new Float32Array(this.maxBars * FLOATS_PER_INSTANCE); + + // --- VAO: unit quad + instanced data --- + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + // Quad vertices (2 triangles) + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Instance buffer (dynamic) + this.instanceBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + this.maxBars * BYTES_PER_INSTANCE, + gl.DYNAMIC_DRAW, + ); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 3, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribDivisor(1, 1); + + gl.bindVertexArray(null); + } + + /** Rebuild bar instance data from current unit state. */ + updateBars( + mobileUnits: Map, + structures: Map, + gameTick: number, + ): void { + this.healthCount = 0; + this.progressCount = 0; + + // --- Health bars (warships) --- + for (const unit of mobileUnits.values()) { + if ( + unit.health === null || + unit.health <= 0 || + unit.health >= WARSHIP_MAX_HEALTH + ) + continue; + this.pushHealth(unit, unit.health / WARSHIP_MAX_HEALTH); + } + + // --- Progress bars (structures) --- + for (const unit of structures.values()) { + const progress = this.computeStructureProgress(unit, gameTick); + if (progress !== null) this.pushProgress(unit, progress); + } + } + + /** Render bars. Call once per frame after FX, before names. */ + draw(cameraMat: Float32Array): void { + if (this.healthCount === 0 && this.progressCount === 0) return; + + const gl = this.gl; + const b = this.settings.bar; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMat); + gl.uniform1f(this.uBorderWidth, b.borderWidth); + gl.uniform3f(this.uThresholds, b.threshold1, b.threshold2, b.threshold3); + gl.uniform3f(this.uColorRed, b.colorRedR, b.colorRedG, b.colorRedB); + gl.uniform3f( + this.uColorOrange, + b.colorOrangeR, + b.colorOrangeG, + b.colorOrangeB, + ); + gl.uniform3f( + this.uColorYellow, + b.colorYellowR, + b.colorYellowG, + b.colorYellowB, + ); + gl.uniform3f(this.uColorGreen, b.colorGreenR, b.colorGreenG, b.colorGreenB); + gl.bindVertexArray(this.vao); + + // Health bars + if (this.healthCount > 0) { + gl.uniform2f(this.uBarSize, b.healthBarW, b.healthBarH); + gl.uniform2f(this.uBarOffset, -b.healthBarW / 2, b.healthBarOffsetY); + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.healthData.subarray(0, this.healthCount * FLOATS_PER_INSTANCE), + ); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.healthCount); + } + + // Progress bars + if (this.progressCount > 0) { + gl.uniform2f(this.uBarSize, b.progressBarW, b.progressBarH); + gl.uniform2f(this.uBarOffset, -b.progressBarW / 2, b.progressBarOffsetY); + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.progressData.subarray(0, this.progressCount * FLOATS_PER_INSTANCE), + ); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.progressCount); + } + + gl.bindVertexArray(null); + } + + dispose(): void { + this.gl.deleteProgram(this.program); + this.gl.deleteBuffer(this.instanceBuf); + this.gl.deleteVertexArray(this.vao); + } + + // ---- Private ---- + + private pushHealth(unit: UnitState, progress: number): void { + if (this.healthCount >= this.maxBars) return; + const off = this.healthCount * FLOATS_PER_INSTANCE; + this.healthData[off] = unit.pos % this.mapW; + this.healthData[off + 1] = (unit.pos - this.healthData[off]) / this.mapW; + this.healthData[off + 2] = progress; + this.healthCount++; + } + + private pushProgress(unit: UnitState, progress: number): void { + if (this.progressCount >= this.maxBars) return; + const off = this.progressCount * FLOATS_PER_INSTANCE; + const x = unit.pos % this.mapW; + this.progressData[off] = x; + this.progressData[off + 1] = (unit.pos - x) / this.mapW; + this.progressData[off + 2] = progress; + this.progressCount++; + } + + private computeStructureProgress( + unit: UnitState, + gameTick: number, + ): number | null { + // Deletion progress (reverse countdown — takes priority over other bars) + if (unit.markedForDeletion !== false) { + const remaining = unit.markedForDeletion - gameTick; + return Math.max(0, Math.min(1, remaining / DELETION_MARK_DURATION)); + } + + // Construction progress + if (unit.underConstruction && unit.constructionStartTick !== null) { + const duration = CONSTRUCTION_DURATIONS[unit.unitType] ?? 50; + const elapsed = gameTick - unit.constructionStartTick; + return Math.min(1, Math.max(0, elapsed / duration)); + } + + // Missile readiness (Silo / SAM) + if ( + unit.unitType === UT_MISSILE_SILO || + unit.unitType === UT_SAM_LAUNCHER + ) { + const readiness = missileReadiness( + unit.unitType, + unit.level, + unit.missileTimerQueue, + gameTick, + ); + if (readiness < 1) return readiness; + } + + return null; + } +} diff --git a/src/client/render/gl/passes/border-compute-pass.ts b/src/client/render/gl/passes/border-compute-pass.ts new file mode 100644 index 0000000000..625aefd00c --- /dev/null +++ b/src/client/render/gl/passes/border-compute-pass.ts @@ -0,0 +1,265 @@ +/** + * BorderComputePass — tile-resolution pass that computes per-tile border flags. + * + * Runs a fullscreen quad at tile resolution (mapW × mapH) and writes to an + * RGBA8 texture: + * R = border type: 0 = interior, 0.5 = normal border, 1.0 = highlight border + * G = ember intensity: 0–255 (pre-computed flicker value, 0 = no ember) + * B = defense proximity: 1.0 if border tile is within range of same-owner defense post + * + * Both MapOverlayPass (daytime) and the night stamp overlay read this buffer + * instead of independently computing neighbor checks. Border thickening is + * computed once here via an N-tile Chebyshev radius expansion. + */ + +import type { RenderSettings } from "../render-settings"; +import borderComputeFragSrc from "../shaders/border-compute/border-compute.frag.glsl?raw"; +import fullscreenNoUvVertSrc from "../shaders/shared/fullscreen-no-uv.vert.glsl?raw"; +import { + createFullscreenQuad, + createProgram, + createTexture2D, + shaderSrc, +} from "../utils/gl-utils"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +const MAX_DEFENSE_POSTS = 64; + +/** Max player smallID supported by the relationship texture. */ +const RELATION_TEX_SIZE = 1024; + +// --------------------------------------------------------------------------- +// BorderComputePass +// --------------------------------------------------------------------------- + +export class BorderComputePass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + + private borderTex: WebGLTexture; + private borderFbo: WebGLFramebuffer; + private mapW: number; + private mapH: number; + + private relationTex: WebGLTexture; + + private uMapSize: WebGLUniformLocation; + private uHighlightOwner: WebGLUniformLocation; + private uHighlightThicken: WebGLUniformLocation; + private uTick: WebGLUniformLocation; + private uEmberThresholdUnowned: WebGLUniformLocation; + private uEmberThresholdOwned: WebGLUniformLocation; + private uEmberFlickerSpeed: WebGLUniformLocation; + private uDefensePosts: WebGLUniformLocation; + private uDefensePostCount: WebGLUniformLocation; + private uDefensePostRange: WebGLUniformLocation; + + private highlightOwner = 0; + /** True when any input has changed since last draw. Starts true so first frame computes. */ + private dirty = true; + + /** Packed defense post data: [x, y, ownerID, 0, x, y, ownerID, 0, ...] */ + private defensePostData = new Float32Array(MAX_DEFENSE_POSTS * 4); + private defensePostCount = 0; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + + this.program = createProgram( + gl, + fullscreenNoUvVertSrc, + shaderSrc(borderComputeFragSrc, { ...TILE_DEFINES, MAX_DEFENSE_POSTS }), + ); + + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uHighlightOwner = gl.getUniformLocation( + this.program, + "uHighlightOwner", + )!; + this.uHighlightThicken = gl.getUniformLocation( + this.program, + "uHighlightThicken", + )!; + this.uTick = gl.getUniformLocation(this.program, "uTick")!; + this.uEmberThresholdUnowned = gl.getUniformLocation( + this.program, + "uEmberThresholdUnowned", + )!; + this.uEmberThresholdOwned = gl.getUniformLocation( + this.program, + "uEmberThresholdOwned", + )!; + this.uEmberFlickerSpeed = gl.getUniformLocation( + this.program, + "uEmberFlickerSpeed", + )!; + this.uDefensePosts = gl.getUniformLocation(this.program, "uDefensePosts")!; + this.uDefensePostCount = gl.getUniformLocation( + this.program, + "uDefensePostCount", + )!; + this.uDefensePostRange = gl.getUniformLocation( + this.program, + "uDefensePostRange", + )!; + + // Texture unit binding + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uRelationTex"), 1); + + // --- Relationship texture (R8UI, RELATION_TEX_SIZE × RELATION_TEX_SIZE) --- + this.relationTex = createTexture2D(gl, { + width: RELATION_TEX_SIZE, + height: RELATION_TEX_SIZE, + internalFormat: gl.R8UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_BYTE, + data: null, + filter: gl.NEAREST, + }); + + // --- RGBA8 border buffer at tile resolution --- + // R = border type, G = ember intensity, B = defense proximity flag + this.borderTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.RGBA8, + format: gl.RGBA, + type: gl.UNSIGNED_BYTE, + data: null, + filter: gl.NEAREST, + }); + + // FBO + this.borderFbo = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.borderFbo); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.borderTex, + 0, + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + // Fullscreen quad VAO [0,1] + this.vao = createFullscreenQuad(gl); + + // Store tileTex reference for binding + this._tileTex = tileTex; + } + + private _tileTex: WebGLTexture; + + /** Set the highlighted player's ownerID (0 = no highlight). */ + setHighlightOwner(ownerID: number): void { + if (ownerID === this.highlightOwner) return; + this.highlightOwner = ownerID; + this.dirty = true; + } + + /** + * Upload a relationship matrix (R8UI, size × size). + * Values: 0 = neutral, 1 = friendly, 2 = embargo. + * Indexed by [ownerA, ownerB]. Size must be ≤ RELATION_TEX_SIZE. + */ + updateRelations(data: Uint8Array, size: number): void { + const gl = this.gl; + const s = Math.min(size, RELATION_TEX_SIZE); + gl.bindTexture(gl.TEXTURE_2D, this.relationTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + s, + s, + gl.RED_INTEGER, + gl.UNSIGNED_BYTE, + data, + ); + this.dirty = true; + } + + /** Update defense post positions for checkerboard proximity. */ + updateDefensePosts(posts: { x: number; y: number; ownerID: number }[]): void { + const count = Math.min(posts.length, MAX_DEFENSE_POSTS); + const data = this.defensePostData; + for (let i = 0; i < count; i++) { + const p = posts[i]; + const off = i * 4; + data[off] = p.x; + data[off + 1] = p.y; + data[off + 2] = p.ownerID; + data[off + 3] = 0; + } + this.defensePostCount = count; + this.dirty = true; + } + + /** Notify that the tile texture has been updated (ownership may have changed). */ + notifyTilesChanged(): void { + this.dirty = true; + } + + /** The border buffer texture (RG8, tile resolution). */ + getBorderTex(): WebGLTexture { + return this.borderTex; + } + + /** + * Compute border flags for the current frame. Call before MapOverlayPass and stamp overlay. + * Leaves the GL state with its own FBO bound — caller must restore FBO and viewport. + */ + draw(tick: number): void { + if (!this.dirty) return; + this.dirty = false; + + const gl = this.gl; + const mo = this.settings.mapOverlay; + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.borderFbo); + gl.viewport(0, 0, this.mapW, this.mapH); + gl.disable(gl.BLEND); + + gl.useProgram(this.program); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1ui(this.uHighlightOwner, this.highlightOwner); + gl.uniform1i(this.uHighlightThicken, Math.floor(mo.highlightThicken)); + gl.uniform1f(this.uTick, tick); + gl.uniform1f(this.uEmberThresholdUnowned, mo.emberThresholdUnowned); + gl.uniform1f(this.uEmberThresholdOwned, mo.emberThresholdOwned); + gl.uniform1f(this.uEmberFlickerSpeed, mo.emberFlickerSpeed); + gl.uniform4fv(this.uDefensePosts, this.defensePostData); + gl.uniform1i(this.uDefensePostCount, this.defensePostCount); + gl.uniform1f(this.uDefensePostRange, mo.defensePostRange); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this._tileTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.relationTex); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteTexture(this.borderTex); + gl.deleteTexture(this.relationTex); + gl.deleteFramebuffer(this.borderFbo); + } +} diff --git a/src/client/render/gl/passes/border-stamp-pass.ts b/src/client/render/gl/passes/border-stamp-pass.ts new file mode 100644 index 0000000000..4d247c1141 --- /dev/null +++ b/src/client/render/gl/passes/border-stamp-pass.ts @@ -0,0 +1,162 @@ +/** + * BorderStampPass — territory borders + defense checkerboard + embers. + * + * Always draws at full brightness (after the optional night composite). + * Reads pre-computed border flags, ember intensity, and defense proximity + * from the BorderComputePass RGBA8 buffer. + */ + +import type { RenderSettings } from "../render-settings"; +import { getPaletteSize } from "../utils/color-utils"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +import borderStampFragSrc from "../shaders/day-night/border-stamp.frag.glsl?raw"; +import borderStampVertSrc from "../shaders/day-night/border-stamp.vert.glsl?raw"; + +export class BorderStampPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + + private program: WebGLProgram; + private uCam: WebGLUniformLocation; + private uMapSize: WebGLUniformLocation; + private uHighlightBrighten: WebGLUniformLocation; + private uDefenseCheckerDarken: WebGLUniformLocation; + private uEmbargoTintRatio: WebGLUniformLocation; + private uFriendlyTintRatio: WebGLUniformLocation; + private uEmberColorDark: WebGLUniformLocation; + private uEmberColorBright: WebGLUniformLocation; + private uEmberStrengthUnowned: WebGLUniformLocation; + private uAltView: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private tileTex: WebGLTexture; + private paletteTex: WebGLTexture; + private borderTex: WebGLTexture; + private affiliationTex: WebGLTexture | null = null; + private altView = false; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + paletteTex: WebGLTexture, + borderTex: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.paletteTex = paletteTex; + this.borderTex = borderTex; + + this.program = createProgram( + gl, + borderStampVertSrc, + shaderSrc(borderStampFragSrc, { + PALETTE_SIZE: getPaletteSize(), + ...TILE_DEFINES, + }), + ); + this.uCam = gl.getUniformLocation(this.program, "uCamera")!; + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uHighlightBrighten = gl.getUniformLocation( + this.program, + "uHighlightBrighten", + )!; + this.uDefenseCheckerDarken = gl.getUniformLocation( + this.program, + "uDefenseCheckerDarken", + )!; + this.uEmbargoTintRatio = gl.getUniformLocation( + this.program, + "uEmbargoTintRatio", + )!; + this.uFriendlyTintRatio = gl.getUniformLocation( + this.program, + "uFriendlyTintRatio", + )!; + this.uEmberColorDark = gl.getUniformLocation( + this.program, + "uEmberColorDark", + )!; + this.uEmberColorBright = gl.getUniformLocation( + this.program, + "uEmberColorBright", + )!; + this.uEmberStrengthUnowned = gl.getUniformLocation( + this.program, + "uEmberStrengthUnowned", + )!; + this.uAltView = gl.getUniformLocation(this.program, "uAltView")!; + + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uPalette"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uBorderTex"), 2); + gl.uniform1i(gl.getUniformLocation(this.program, "uAffiliation"), 3); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + setAltView(active: boolean): void { + this.altView = active; + } + setAffiliationTex(tex: WebGLTexture): void { + this.affiliationTex = tex; + } + + /** Draw borders + defense checkerboard + embers. Blending must be enabled. */ + draw(cameraMatrix: Float32Array): void { + const gl = this.gl; + const mo = this.settings.mapOverlay; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCam, false, cameraMatrix); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1f(this.uHighlightBrighten, mo.highlightBrighten); + gl.uniform1f(this.uDefenseCheckerDarken, mo.defenseCheckerDarken); + gl.uniform1f(this.uEmbargoTintRatio, mo.embargoTintRatio); + gl.uniform1f(this.uFriendlyTintRatio, mo.friendlyTintRatio); + gl.uniform3f( + this.uEmberColorDark, + mo.emberColorDarkR, + mo.emberColorDarkG, + mo.emberColorDarkB, + ); + gl.uniform3f( + this.uEmberColorBright, + mo.emberColorBrightR, + mo.emberColorBrightG, + mo.emberColorBrightB, + ); + gl.uniform1f(this.uEmberStrengthUnowned, mo.emberStrengthUnowned); + gl.uniform1i(this.uAltView, this.altView ? 1 : 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.borderTex); + if (this.affiliationTex) { + gl.activeTexture(gl.TEXTURE3); + gl.bindTexture(gl.TEXTURE_2D, this.affiliationTex); + } + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/conquest-popup-pass.ts b/src/client/render/gl/passes/conquest-popup-pass.ts new file mode 100644 index 0000000000..01cb68640c --- /dev/null +++ b/src/client/render/gl/passes/conquest-popup-pass.ts @@ -0,0 +1,412 @@ +/** + * ConquestPopupPass — MSDF-rendered floating text popups. + * + * Renders two kinds of popups using the same MSDF atlas as NamePass: + * - Conquest popups: "+ 500" gold text at conquered player locations (static position, fade only) + * - Bonus popups: "+ 45K" income text at port tiles (rises upward + fades) + */ + +import type { BonusEvent, ConquestFx } from "../../types"; +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; +import type { GlyphTables } from "./name-pass/atlas-data"; +import { buildGlyphTables, parseAtlasData } from "./name-pass/atlas-data"; +import { buildGlyphMetricsTex } from "./name-pass/data-textures"; +import { layoutString } from "./name-pass/text-layout"; +import { CHAR_RANGE, MAX_CHARS } from "./name-pass/types"; + +import atlasUrl from "../assets/msdf-atlas.png?url"; +import fragSrc from "../shaders/conquest-popup/conquest-popup.frag.glsl?raw"; +import vertSrc from "../shaders/conquest-popup/conquest-popup.vert.glsl?raw"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// worldX, worldY, cursorX, charCode, alpha, colorR, colorG, colorB, scale, outlineWidth +const FLOATS_PER_INSTANCE = 10; +const BYTES_PER_INSTANCE = FLOATS_PER_INSTANCE * 4; +const CONQUEST_LIFETIME_MS = 2500; +/** Nominal game tick rate — 100ms per tick. */ +const MS_PER_TICK = 100; +/** Tiles below conquered name location (matches upstream DynamicUILayer). */ +const CONQUEST_Y_OFFSET = 8; +/** World-space font size for conquest popups. */ +const CONQUEST_SCALE = 6; +const CONQUEST_OUTLINE_WIDTH = 2.0; + +// --------------------------------------------------------------------------- +// Active popup tracking +// --------------------------------------------------------------------------- + +interface ActivePopup { + x: number; + y: number; + text: string; + startMs: number; + lifetimeMs: number; + riseSpeed: number; // world units per second (0 = no rise) + colorR: number; + colorG: number; + colorB: number; + scale: number; + outlineWidth: number; +} + +function formatGold(gold: number): string { + if (gold >= 1_000_000) return (gold / 1_000_000).toFixed(1) + "M"; + if (gold >= 1_000) return (gold / 1_000).toFixed(1) + "K"; + return gold.toString(); +} + +// --------------------------------------------------------------------------- +// ConquestPopupPass +// --------------------------------------------------------------------------- + +export class ConquestPopupPass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private maxInstances = 512; + + // Uniform locations + private uCamera: WebGLUniformLocation; + private uZoom: WebGLUniformLocation; + private uMinScreenScale: WebGLUniformLocation; + private uDistRange: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private instanceBuf: WebGLBuffer; + private instanceData: Float32Array; + private instanceCount = 0; + + private glyphMetricsTex: WebGLTexture; + private atlasTex: WebGLTexture | null = null; + private atlasReady = false; + + // CPU-side glyph tables for layoutString + private glyph: GlyphTables; + private kernTable: Int8Array; + + // Reusable buffers for layoutString + private charCodes = new Uint8Array(MAX_CHARS); + private cursors = new Float32Array(MAX_CHARS); + + private distanceRange: number; + private fontSize: number; + private atlasScaleH: number; + private base: number; + + // Active popups (both conquest and bonus, unified) + private active: ActivePopup[] = []; + + // Settings reference + private settings: RenderSettings; + + // Map width for tile→x/y conversion + private mapW = 0; + + // Pluggable time source (same pattern as FxPass) + private timeFn: () => number = () => performance.now(); + private now(): number { + return this.timeFn(); + } + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + + // Parse atlas data (shared with NamePass/StructureLevelPass) + const atlas = parseAtlasData(); + this.glyph = buildGlyphTables(atlas.chars); + this.kernTable = new Int8Array(CHAR_RANGE * CHAR_RANGE); + this.distanceRange = atlas.distanceRange; + this.fontSize = atlas.fontSize; + this.atlasScaleH = atlas.scaleH; + this.base = atlas.base; + + // Compile shaders + this.program = createProgram(gl, vertSrc, fragSrc); + + // Texture unit bindings + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uAtlas"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uGlyphMetrics"), 1); + + // Static uniforms + gl.uniform1f( + gl.getUniformLocation(this.program, "uFontSize")!, + this.fontSize, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uAtlasScaleH")!, + this.atlasScaleH, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uBase")!, this.base); + + // Dynamic uniform locations + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uZoom = gl.getUniformLocation(this.program, "uZoom")!; + this.uMinScreenScale = gl.getUniformLocation( + this.program, + "uMinScreenScale", + )!; + this.uDistRange = gl.getUniformLocation(this.program, "uDistRange")!; + + // Glyph metrics data texture + this.glyphMetricsTex = buildGlyphMetricsTex(gl, atlas); + + // Start async MSDF atlas load + this.loadAtlas(); + + // Instance buffer + this.instanceData = new Float32Array( + this.maxInstances * FLOATS_PER_INSTANCE, + ); + this.instanceBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + this.instanceData.byteLength, + gl.DYNAMIC_DRAW, + ); + + // VAO + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + // Attribute 0: unit quad [0,1]² + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Per-instance attributes from instance buffer + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + // Attribute 1: vec4 (worldX, worldY, cursorX, charCode) at offset 0 + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribDivisor(1, 1); + // Attribute 2: vec4 (alpha, colorR, colorG, colorB) at offset 16 + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 4, gl.FLOAT, false, BYTES_PER_INSTANCE, 16); + gl.vertexAttribDivisor(2, 1); + // Attribute 3: vec2 (scale, outlineWidth) at offset 32 + gl.enableVertexAttribArray(3); + gl.vertexAttribPointer(3, 2, gl.FLOAT, false, BYTES_PER_INSTANCE, 32); + gl.vertexAttribDivisor(3, 1); + + gl.bindVertexArray(null); + } + + private loadAtlas(): void { + const img = new Image(); + img.onload = () => { + const gl = this.gl; + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + this.atlasTex = tex; + this.atlasReady = true; + }; + img.src = atlasUrl; + } + + setMapWidth(w: number): void { + this.mapW = w; + } + + // ------------------------------------------------------------------------- + // Event input + // ------------------------------------------------------------------------- + + applyConquestEvents(events: ConquestFx[]): void { + const now = this.now(); + for (const evt of events) { + const startMs = now - (evt.tickAge ?? 0) * MS_PER_TICK; + if (now - startMs >= CONQUEST_LIFETIME_MS) continue; + this.active.push({ + x: evt.x, + y: evt.y + CONQUEST_Y_OFFSET, + text: "+ " + formatGold(evt.gold), + startMs, + lifetimeMs: CONQUEST_LIFETIME_MS, + riseSpeed: 0, + colorR: 1, + colorG: 1, + colorB: 1, + scale: CONQUEST_SCALE, + outlineWidth: CONQUEST_OUTLINE_WIDTH, + }); + } + } + + applyBonusEvents(events: BonusEvent[]): void { + if (this.mapW === 0) return; + const now = this.now(); + const s = this.settings.bonusPopup; + for (const evt of events) { + if (evt.gold === 0) continue; + const x = evt.tile % this.mapW; + const y = Math.floor(evt.tile / this.mapW); + const sign = evt.gold >= 0 ? "+" : "-"; + this.active.push({ + x, + y: y + s.yOffset, + text: sign + " " + formatGold(Math.abs(evt.gold)), + startMs: now, + lifetimeMs: s.lifetimeMs, + riseSpeed: s.riseSpeed, + colorR: s.colorR, + colorG: s.colorG, + colorB: s.colorB, + scale: s.scale, + outlineWidth: s.outlineWidth, + }); + } + } + + // ------------------------------------------------------------------------- + // Tick — cull expired, rebuild instance buffer + // ------------------------------------------------------------------------- + + tick(): void { + if (this.active.length === 0) return; + const now = this.now(); + + // Remove expired popups (swap-remove) + for (let i = this.active.length - 1; i >= 0; i--) { + if (now - this.active[i].startMs >= this.active[i].lifetimeMs) { + this.active[i] = this.active[this.active.length - 1]; + this.active.pop(); + } + } + + this.rebuildInstances(now); + } + + private rebuildInstances(now: number): void { + let count = 0; + + for (const popup of this.active) { + const elapsed = now - popup.startMs; + const alpha = Math.max(0, 1 - elapsed / popup.lifetimeMs); + if (alpha <= 0) continue; + + // Rise animation: move upward over time + const riseY = + popup.riseSpeed > 0 + ? popup.y - (elapsed / 1000) * popup.riseSpeed + : popup.y; + + layoutString( + popup.text, + this.glyph, + this.kernTable, + this.charCodes, + this.cursors, + ); + const len = Math.min(popup.text.length, MAX_CHARS); + + for (let i = 0; i < len; i++) { + if (this.charCodes[i] === 0) continue; + if (count >= this.maxInstances) { + this.growBuffer(); + } + + const off = count * FLOATS_PER_INSTANCE; + this.instanceData[off + 0] = popup.x; + this.instanceData[off + 1] = riseY; + this.instanceData[off + 2] = this.cursors[i]; + this.instanceData[off + 3] = this.charCodes[i]; + this.instanceData[off + 4] = alpha; + this.instanceData[off + 5] = popup.colorR; + this.instanceData[off + 6] = popup.colorG; + this.instanceData[off + 7] = popup.colorB; + this.instanceData[off + 8] = popup.scale; + this.instanceData[off + 9] = popup.outlineWidth; + count++; + } + } + + this.instanceCount = count; + } + + private growBuffer(): void { + this.maxInstances *= 2; + const newData = new Float32Array(this.maxInstances * FLOATS_PER_INSTANCE); + newData.set(this.instanceData); + this.instanceData = newData; + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + this.instanceData.byteLength, + gl.DYNAMIC_DRAW, + ); + } + + // ------------------------------------------------------------------------- + // Draw + // ------------------------------------------------------------------------- + + draw(cameraMatrix: Float32Array, zoom: number): void { + if (!this.atlasReady || this.instanceCount === 0) return; + if (zoom < this.settings.bonusPopup.cullZoom) return; + + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uZoom, zoom); + gl.uniform1f(this.uMinScreenScale, this.settings.bonusPopup.minScreenScale); + gl.uniform1f(this.uDistRange, this.distanceRange); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex!); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.glyphMetricsTex); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceData, + 0, + this.instanceCount * FLOATS_PER_INSTANCE, + ); + + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); + } + + // ------------------------------------------------------------------------- + // Lifecycle + // ------------------------------------------------------------------------- + + /** Override the time source. Default: performance.now (wall clock). */ + setTimeFn(fn: () => number): void { + this.timeFn = fn; + } + + clear(): void { + this.active.length = 0; + this.instanceCount = 0; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteBuffer(this.instanceBuf); + gl.deleteVertexArray(this.vao); + gl.deleteTexture(this.glyphMetricsTex); + if (this.atlasTex) gl.deleteTexture(this.atlasTex); + } +} diff --git a/src/client/render/gl/passes/coordinate-grid-pass.ts b/src/client/render/gl/passes/coordinate-grid-pass.ts new file mode 100644 index 0000000000..b9a9f0e2f3 --- /dev/null +++ b/src/client/render/gl/passes/coordinate-grid-pass.ts @@ -0,0 +1,135 @@ +/** + * CoordinateGridPass — procedural grid overlay with cell labels. + * + * Draws white grid lines at cell boundaries and alphanumeric labels + * (A1, B2, ...) at the top-left of each cell. Grid computation matches + * the upstream game's CoordinateGridLayer. + */ + +import type { RenderSettings } from "../render-settings"; +import { createMapQuad, createProgram } from "../utils/gl-utils"; + +import gridFragSrc from "../shaders/grid/grid.frag.glsl?raw"; +import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; + +const BASE_CELL_COUNT = 10; +const MAX_COLUMNS = 50; +const MIN_ROWS = 2; + +const GLYPH_W = 24; +const GLYPH_H = 36; +const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +export class CoordinateGridPass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + private glyphTex: WebGLTexture; + + private uCamera: WebGLUniformLocation; + private uMapSize: WebGLUniformLocation; + private uCellSize: WebGLUniformLocation; + private uZoom: WebGLUniformLocation; + private uFontSize: WebGLUniformLocation; + + private mapW: number; + private mapH: number; + private cellSize: number; + private settings: RenderSettings; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + settings: RenderSettings, + ) { + this.gl = gl; + this.mapW = mapW; + this.mapH = mapH; + this.cellSize = computeCellSize(mapW, mapH); + this.settings = settings; + + this.glyphTex = this.createGlyphAtlas(); + + this.program = createProgram(gl, overlayVertSrc, gridFragSrc); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uCellSize = gl.getUniformLocation(this.program, "uCellSize")!; + this.uZoom = gl.getUniformLocation(this.program, "uZoom")!; + this.uFontSize = gl.getUniformLocation(this.program, "uFontSize")!; + + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uGlyphTex"), 0); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + draw(cameraMatrix: Float32Array, zoom: number): void { + const gl = this.gl; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1f(this.uCellSize, this.cellSize); + gl.uniform1f(this.uZoom, zoom); + gl.uniform1f(this.uFontSize, this.settings.altView.gridFontSize); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.glyphTex); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + gl.deleteTexture(this.glyphTex); + } + + /** Render A-Z, 0-9 glyphs into a single-row texture atlas. */ + private createGlyphAtlas(): WebGLTexture { + const canvas = document.createElement("canvas"); + canvas.width = CHARS.length * GLYPH_W; + canvas.height = GLYPH_H; + + const ctx = canvas.getContext("2d")!; + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "white"; + ctx.font = `bold ${GLYPH_H - 8}px monospace`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + + for (let i = 0; i < CHARS.length; i++) { + ctx.fillText(CHARS[i], i * GLYPH_W + GLYPH_W / 2, GLYPH_H / 2); + } + + const gl = this.gl; + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + return tex; + } +} + +/** Compute cell size matching upstream CoordinateGridLayer.computeGrid(). */ +function computeCellSize(mapW: number, mapH: number): number { + const raw = Math.min(mapW, mapH) / BASE_CELL_COUNT; + let rows = Math.max(1, Math.round(mapH / raw)); + let cols = Math.max(1, Math.round(mapW / raw)); + + if (cols > MAX_COLUMNS) { + const maxRows = Math.floor((MAX_COLUMNS * mapH) / mapW); + rows = Math.max(MIN_ROWS, Math.min(rows, maxRows)); + cols = MAX_COLUMNS; + } + + return Math.min(mapW / cols, mapH / rows); +} diff --git a/src/client/render/gl/passes/crosshair-pass.ts b/src/client/render/gl/passes/crosshair-pass.ts new file mode 100644 index 0000000000..9d07fe370f --- /dev/null +++ b/src/client/render/gl/passes/crosshair-pass.ts @@ -0,0 +1,96 @@ +/** + * CrosshairPass — renders a red crosshair at the cursor position during + * warship or MIRV placement (ghost preview). + * + * Screen-space quad with a crosshair SDF in the fragment shader. + * Darker red when placement is invalid. + */ + +import type { GhostPreviewData } from "../../types"; +import { UT_MIRV, UT_WARSHIP } from "../../types"; +import { createProgram } from "../utils/gl-utils"; + +import fragSrc from "../shaders/crosshair/crosshair.frag.glsl?raw"; +import vertSrc from "../shaders/crosshair/crosshair.vert.glsl?raw"; + +/** Half-size of the crosshair quad in screen pixels. */ +const CROSSHAIR_PX = 20; + +export class CrosshairPass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + + private uCamera: WebGLUniformLocation; + private uCenter: WebGLUniformLocation; + private uHalfSize: WebGLUniformLocation; + private uViewport: WebGLUniformLocation; + private uColor: WebGLUniformLocation; + + private active = false; + private centerX = 0; + private centerY = 0; + private canBuild = false; + + constructor(gl: WebGL2RenderingContext) { + this.gl = gl; + this.program = createProgram(gl, vertSrc, fragSrc); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uCenter = gl.getUniformLocation(this.program, "uCenter")!; + this.uHalfSize = gl.getUniformLocation(this.program, "uHalfSize")!; + this.uViewport = gl.getUniformLocation(this.program, "uViewport")!; + this.uColor = gl.getUniformLocation(this.program, "uColor")!; + + // Unit quad [0,1] + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + } + + updateGhostPreview(data: GhostPreviewData | null): void { + if (data && (data.ghostType === UT_WARSHIP || data.ghostType === UT_MIRV)) { + this.active = true; + this.centerX = data.tileX; + this.centerY = data.tileY; + this.canBuild = data.canBuild || data.canUpgrade; + } else { + this.active = false; + } + } + + draw(cameraMatrix: Float32Array): void { + if (!this.active) return; + + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uCenter, this.centerX, this.centerY); + gl.uniform1f(this.uHalfSize, CROSSHAIR_PX); + gl.uniform2f(this.uViewport, gl.drawingBufferWidth, gl.drawingBufferHeight); + + if (this.canBuild) { + gl.uniform3f(this.uColor, 0.9, 0.15, 0.15); // red crosshair + } else { + gl.uniform3f(this.uColor, 0.4, 0.1, 0.1); // dark red = can't build + } + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/fallout-bloom-pass.ts b/src/client/render/gl/passes/fallout-bloom-pass.ts new file mode 100644 index 0000000000..62c6b27f71 --- /dev/null +++ b/src/client/render/gl/passes/fallout-bloom-pass.ts @@ -0,0 +1,320 @@ +/** + * FalloutBloomPass — soft radioactive glow around irradiated tiles. + * + * Tile-space pipeline (camera-independent, zero shimmer): + * 1. Extract — compute per-tile bloom at map resolution (mapW x mapH) + * 2. Blur — two iterations of separable 9-tap Gaussian in tile space + * 3. Composite — camera-projected map quad samples blurred texture (LINEAR) + * + * Heat management is handled by HeatManager (shared with LightmapPass). + */ + +import type { RenderSettings } from "../render-settings"; +import { + createFullscreenQuad, + createMapQuad, + createProgram, + shaderSrc, +} from "../utils/gl-utils"; +import type { HeatManager } from "../utils/heat-manager"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +import compositeFragSrc from "../shaders/fallout-bloom/composite.frag.glsl?raw"; +import compositeVertSrc from "../shaders/fallout-bloom/composite.vert.glsl?raw"; +import extractFragSrc from "../shaders/fallout-bloom/extract.frag.glsl?raw"; +import blurFragSrc from "../shaders/shared/blur.frag.glsl?raw"; +import fullscreenNoUvVertSrc from "../shaders/shared/fullscreen-no-uv.vert.glsl?raw"; +import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw"; + +export class FalloutBloomPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + private tileTex: WebGLTexture; + private heatManager: HeatManager; + + // Programs + private extractProg: WebGLProgram; + private blurProg: WebGLProgram; + private compositeProg: WebGLProgram; + + // Uniforms — extract + private uExtractMapSize: WebGLUniformLocation; + private uExtractTick: WebGLUniformLocation; + private uBroilSpeedCold: WebGLUniformLocation; + private uBroilSpeedHot: WebGLUniformLocation; + private uNoiseFreq1: WebGLUniformLocation; + private uNoiseFreq2: WebGLUniformLocation; + private uContrastLoCold: WebGLUniformLocation; + private uContrastLoHot: WebGLUniformLocation; + private uContrastHiCold: WebGLUniformLocation; + private uContrastHiHot: WebGLUniformLocation; + private uMetaFreq: WebGLUniformLocation; + private uIntensityCold: WebGLUniformLocation; + private uIntensityHot: WebGLUniformLocation; + private uMetaInfluenceCold: WebGLUniformLocation; + private uMetaInfluenceHot: WebGLUniformLocation; + private uOpacityFadeEnd: WebGLUniformLocation; + private uBloomColor: WebGLUniformLocation; + + // Uniforms — composite + private uCompositeCam: WebGLUniformLocation; + private uCompositeMapSize: WebGLUniformLocation; + private uBloomCoverage: WebGLUniformLocation; + + // Uniforms — blur + private uBlurDir: WebGLUniformLocation; + + // FBOs (map resolution — fixed size) + private fboA: WebGLFramebuffer; + private fboB: WebGLFramebuffer; + private texA: WebGLTexture; + private texB: WebGLTexture; + + // Geometry + private mapVao: WebGLVertexArrayObject; + private quadVao: WebGLVertexArrayObject; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + heatManager: HeatManager, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.heatManager = heatManager; + + // --- Extract program (tile-space, no camera) --- + this.extractProg = createProgram( + gl, + fullscreenNoUvVertSrc, + shaderSrc(extractFragSrc, TILE_DEFINES), + ); + this.uExtractMapSize = gl.getUniformLocation(this.extractProg, "uMapSize")!; + this.uExtractTick = gl.getUniformLocation(this.extractProg, "uTick")!; + this.uBroilSpeedCold = gl.getUniformLocation( + this.extractProg, + "uBroilSpeedCold", + )!; + this.uBroilSpeedHot = gl.getUniformLocation( + this.extractProg, + "uBroilSpeedHot", + )!; + this.uNoiseFreq1 = gl.getUniformLocation(this.extractProg, "uNoiseFreq1")!; + this.uNoiseFreq2 = gl.getUniformLocation(this.extractProg, "uNoiseFreq2")!; + this.uContrastLoCold = gl.getUniformLocation( + this.extractProg, + "uContrastLoCold", + )!; + this.uContrastLoHot = gl.getUniformLocation( + this.extractProg, + "uContrastLoHot", + )!; + this.uContrastHiCold = gl.getUniformLocation( + this.extractProg, + "uContrastHiCold", + )!; + this.uContrastHiHot = gl.getUniformLocation( + this.extractProg, + "uContrastHiHot", + )!; + this.uMetaFreq = gl.getUniformLocation(this.extractProg, "uMetaFreq")!; + this.uIntensityCold = gl.getUniformLocation( + this.extractProg, + "uIntensityCold", + )!; + this.uIntensityHot = gl.getUniformLocation( + this.extractProg, + "uIntensityHot", + )!; + this.uMetaInfluenceCold = gl.getUniformLocation( + this.extractProg, + "uMetaInfluenceCold", + )!; + this.uMetaInfluenceHot = gl.getUniformLocation( + this.extractProg, + "uMetaInfluenceHot", + )!; + this.uOpacityFadeEnd = gl.getUniformLocation( + this.extractProg, + "uOpacityFadeEnd", + )!; + this.uBloomColor = gl.getUniformLocation(this.extractProg, "uBloomColor")!; + gl.useProgram(this.extractProg); + gl.uniform1i(gl.getUniformLocation(this.extractProg, "uTileTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.extractProg, "uHeatTex"), 1); + + // --- Blur program --- + this.blurProg = createProgram(gl, fullscreenVertSrc, blurFragSrc); + this.uBlurDir = gl.getUniformLocation(this.blurProg, "uDir")!; + gl.useProgram(this.blurProg); + gl.uniform1i(gl.getUniformLocation(this.blurProg, "uTex"), 0); + + // --- Composite program (camera-projected map quad) --- + this.compositeProg = createProgram(gl, compositeVertSrc, compositeFragSrc); + this.uCompositeCam = gl.getUniformLocation(this.compositeProg, "uCamera")!; + this.uCompositeMapSize = gl.getUniformLocation( + this.compositeProg, + "uMapSize", + )!; + this.uBloomCoverage = gl.getUniformLocation( + this.compositeProg, + "uBloomCoverage", + )!; + gl.useProgram(this.compositeProg); + gl.uniform1i(gl.getUniformLocation(this.compositeProg, "uTex"), 0); + + // --- FBO textures (map resolution) --- + this.texA = this.createBloomTex(mapW, mapH); + this.texB = this.createBloomTex(mapW, mapH); + this.fboA = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.fboA); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.texA, + 0, + ); + this.fboB = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.fboB); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.texB, + 0, + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + // --- Geometry --- + this.mapVao = createMapQuad(gl, mapW, mapH); + this.quadVao = createFullscreenQuad(gl); + } + + private createBloomTex(w: number, h: number): WebGLTexture { + const gl = this.gl; + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + w, + h, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + return tex; + } + + /** Run the full extract → blur → composite pipeline. */ + draw(cameraMatrix: Float32Array, tick: number): void { + const gl = this.gl; + const canvas = gl.canvas as HTMLCanvasElement; + const cw = canvas.width; + const ch = canvas.height; + const mw = this.mapW; + const mh = this.mapH; + + // --- 1. Extract: tile-space bloom --- + gl.bindFramebuffer(gl.FRAMEBUFFER, this.fboA); + gl.viewport(0, 0, mw, mh); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.disable(gl.BLEND); + + gl.useProgram(this.extractProg); + gl.uniform2f(this.uExtractMapSize, mw, mh); + gl.uniform1f(this.uExtractTick, tick); + + const fb = this.settings.falloutBloom; + gl.uniform1f(this.uBroilSpeedCold, fb.broilSpeedCold); + gl.uniform1f(this.uBroilSpeedHot, fb.broilSpeedHot); + gl.uniform1f(this.uNoiseFreq1, fb.noiseFreq1); + gl.uniform1f(this.uNoiseFreq2, fb.noiseFreq2); + gl.uniform1f(this.uContrastLoCold, fb.contrastLoCold); + gl.uniform1f(this.uContrastLoHot, fb.contrastLoHot); + gl.uniform1f(this.uContrastHiCold, fb.contrastHiCold); + gl.uniform1f(this.uContrastHiHot, fb.contrastHiHot); + gl.uniform1f(this.uMetaFreq, fb.metaFreq); + gl.uniform1f(this.uIntensityCold, fb.intensityCold); + gl.uniform1f(this.uIntensityHot, fb.intensityHot); + gl.uniform1f(this.uMetaInfluenceCold, fb.metaInfluenceCold); + gl.uniform1f(this.uMetaInfluenceHot, fb.metaInfluenceHot); + gl.uniform1f(this.uOpacityFadeEnd, fb.opacityFadeEnd); + gl.uniform3f(this.uBloomColor, fb.bloomR, fb.bloomG, fb.bloomB); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.heatManager.getHeatTex()); + gl.bindVertexArray(this.quadVao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + // --- 2. Blur: 2 iterations of separable H+V Gaussian --- + gl.useProgram(this.blurProg); + gl.bindVertexArray(this.quadVao); + + for (let iter = 0; iter < 2; iter++) { + gl.bindFramebuffer(gl.FRAMEBUFFER, this.fboB); + gl.viewport(0, 0, mw, mh); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(this.uBlurDir, 1.0 / mw, 0); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.texA); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.fboA); + gl.viewport(0, 0, mw, mh); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(this.uBlurDir, 0, 1.0 / mh); + gl.bindTexture(gl.TEXTURE_2D, this.texB); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + // --- 3. Composite: camera-projected map quad → screen --- + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.viewport(0, 0, cw, ch); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + + gl.useProgram(this.compositeProg); + gl.uniformMatrix3fv(this.uCompositeCam, false, cameraMatrix); + gl.uniform2f(this.uCompositeMapSize, mw, mh); + gl.uniform1f(this.uBloomCoverage, fb.bloomCoverage); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.texA); + gl.bindVertexArray(this.mapVao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + // Restore standard alpha blending + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.extractProg); + gl.deleteProgram(this.blurProg); + gl.deleteProgram(this.compositeProg); + gl.deleteFramebuffer(this.fboA); + gl.deleteFramebuffer(this.fboB); + gl.deleteTexture(this.texA); + gl.deleteTexture(this.texB); + gl.deleteVertexArray(this.mapVao); + gl.deleteVertexArray(this.quadVao); + } +} diff --git a/src/client/render/gl/passes/fallout-light-pass.ts b/src/client/render/gl/passes/fallout-light-pass.ts new file mode 100644 index 0000000000..15828cfe58 --- /dev/null +++ b/src/client/render/gl/passes/fallout-light-pass.ts @@ -0,0 +1,235 @@ +/** + * FalloutLightPass — tile-space fallout light extraction + composite. + * + * Extracted from LightmapPass. Two-step: + * 1. Extract fallout light at tile resolution (mapW x mapH) — reads heat + embers + * 2. Composite into the target lightmap FBO via camera-projected map quad (additive) + */ + +import type { RenderSettings } from "../render-settings"; +import { + createFullscreenQuad, + createMapQuad, + createProgram, + shaderSrc, +} from "../utils/gl-utils"; +import type { HeatManager } from "../utils/heat-manager"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +import falloutCompositeFragSrc from "../shaders/day-night/fallout-composite.frag.glsl?raw"; +import falloutCompositeVertSrc from "../shaders/day-night/fallout-composite.vert.glsl?raw"; +import falloutLightFragSrc from "../shaders/day-night/fallout-light.frag.glsl?raw"; +import fullscreenNoUvVertSrc from "../shaders/shared/fullscreen-no-uv.vert.glsl?raw"; + +export class FalloutLightPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + private heatManager: HeatManager; + private tileTex: WebGLTexture; + private borderTex: WebGLTexture; + + // Fallout light extraction + private falloutLightProg: WebGLProgram; + private uFalloutMapSize: WebGLUniformLocation; + private uFalloutLightColor: WebGLUniformLocation; + private uFalloutLightIntensity: WebGLUniformLocation; + private uFalloutLightThreshold: WebGLUniformLocation; + private uEmberLightColor: WebGLUniformLocation; + private uEmberLightIntensity: WebGLUniformLocation; + + // Fallout composite (tile-space → lightmap) + private falloutCompositeProg: WebGLProgram; + private uFalloutCompositeCam: WebGLUniformLocation; + private uFalloutCompositeMapSize: WebGLUniformLocation; + + // Tile-space FBO + private falloutFbo: WebGLFramebuffer; + private falloutTex: WebGLTexture; + + // Geometry + private quadVao: WebGLVertexArrayObject; + private mapQuadVao: WebGLVertexArrayObject; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + borderTex: WebGLTexture, + heatManager: HeatManager, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.borderTex = borderTex; + this.heatManager = heatManager; + + // Fallout light extraction program + this.falloutLightProg = createProgram( + gl, + fullscreenNoUvVertSrc, + shaderSrc(falloutLightFragSrc, TILE_DEFINES), + ); + this.uFalloutMapSize = gl.getUniformLocation( + this.falloutLightProg, + "uMapSize", + )!; + this.uFalloutLightColor = gl.getUniformLocation( + this.falloutLightProg, + "uFalloutLightColor", + )!; + this.uFalloutLightIntensity = gl.getUniformLocation( + this.falloutLightProg, + "uFalloutLightIntensity", + )!; + this.uFalloutLightThreshold = gl.getUniformLocation( + this.falloutLightProg, + "uFalloutLightThreshold", + )!; + this.uEmberLightColor = gl.getUniformLocation( + this.falloutLightProg, + "uEmberLightColor", + )!; + this.uEmberLightIntensity = gl.getUniformLocation( + this.falloutLightProg, + "uEmberLightIntensity", + )!; + gl.useProgram(this.falloutLightProg); + gl.uniform1i(gl.getUniformLocation(this.falloutLightProg, "uHeatTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.falloutLightProg, "uTileTex"), 1); + gl.uniform1i(gl.getUniformLocation(this.falloutLightProg, "uBorderTex"), 2); + + // Fallout composite program + this.falloutCompositeProg = createProgram( + gl, + falloutCompositeVertSrc, + falloutCompositeFragSrc, + ); + this.uFalloutCompositeCam = gl.getUniformLocation( + this.falloutCompositeProg, + "uCamera", + )!; + this.uFalloutCompositeMapSize = gl.getUniformLocation( + this.falloutCompositeProg, + "uMapSize", + )!; + gl.useProgram(this.falloutCompositeProg); + gl.uniform1i(gl.getUniformLocation(this.falloutCompositeProg, "uTex"), 0); + + // Tile-space FBO (map resolution) + this.falloutTex = this.createRGBA8Tex(mapW, mapH); + this.falloutFbo = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.falloutFbo); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.falloutTex, + 0, + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + // Geometry + this.quadVao = createFullscreenQuad(gl); + this.mapQuadVao = createMapQuad(gl, mapW, mapH); + } + + private createRGBA8Tex(w: number, h: number): WebGLTexture { + const gl = this.gl; + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + w, + h, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + return tex; + } + + /** + * Extract fallout light in tile space, then composite into the target FBO. + * Caller must bind the target FBO and set additive blending before calling. + */ + draw( + cameraMatrix: Float32Array, + targetFbo: WebGLFramebuffer, + targetW: number, + targetH: number, + ): void { + const gl = this.gl; + const dn = this.settings.dayNight; + + // Step 1: Extract fallout light in tile space + gl.bindFramebuffer(gl.FRAMEBUFFER, this.falloutFbo); + gl.viewport(0, 0, this.mapW, this.mapH); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.disable(gl.BLEND); + + gl.useProgram(this.falloutLightProg); + gl.uniform2f(this.uFalloutMapSize, this.mapW, this.mapH); + gl.uniform3f( + this.uFalloutLightColor, + dn.falloutLightR, + dn.falloutLightG, + dn.falloutLightB, + ); + gl.uniform1f(this.uFalloutLightIntensity, dn.falloutLightIntensity); + gl.uniform1f(this.uFalloutLightThreshold, dn.falloutLightThreshold); + gl.uniform3f( + this.uEmberLightColor, + dn.emberLightR, + dn.emberLightG, + dn.emberLightB, + ); + gl.uniform1f(this.uEmberLightIntensity, dn.emberLightIntensity); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.heatManager.getHeatTex()); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.borderTex); + gl.bindVertexArray(this.quadVao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + // Step 2: Composite tile-space fallout into target lightmap + gl.bindFramebuffer(gl.FRAMEBUFFER, targetFbo); + gl.viewport(0, 0, targetW, targetH); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE); // additive + + gl.useProgram(this.falloutCompositeProg); + gl.uniformMatrix3fv(this.uFalloutCompositeCam, false, cameraMatrix); + gl.uniform2f(this.uFalloutCompositeMapSize, this.mapW, this.mapH); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.falloutTex); + gl.bindVertexArray(this.mapQuadVao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.falloutLightProg); + gl.deleteProgram(this.falloutCompositeProg); + gl.deleteFramebuffer(this.falloutFbo); + gl.deleteTexture(this.falloutTex); + gl.deleteVertexArray(this.quadVao); + gl.deleteVertexArray(this.mapQuadVao); + } +} diff --git a/src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts b/src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts new file mode 100644 index 0000000000..bc66eda300 --- /dev/null +++ b/src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts @@ -0,0 +1,212 @@ +/** + * FxAttackRingPass — persistent animated rings at transport ship target tiles. + * + * Rings fade in when a transport acquires a target, and fade out when the + * target is lost. Uses a rotating dashed-ring shader (attack-ring.vert/frag). + */ + +import type { AttackRingInput } from "../../../types"; +import { DynamicInstanceBuffer } from "../../dynamic-buffer"; +import type { RenderSettings } from "../../render-settings"; +import { createProgram } from "../../utils/gl-utils"; + +import attackRingFragSrc from "../../shaders/fx/attack-ring.frag.glsl?raw"; +import attackRingVertSrc from "../../shaders/fx/attack-ring.vert.glsl?raw"; + +export type { AttackRingInput } from "../../../types"; + +// --------------------------------------------------------------------------- +// Active state +// --------------------------------------------------------------------------- + +interface ActiveAttackRing { + unitId: number; + x: number; + y: number; + /** performance.now() when fade-in started or fade-out began. */ + transitionMs: number; + fadingOut: boolean; +} + +// --------------------------------------------------------------------------- +// Instance data layout: x, y, alpha +// --------------------------------------------------------------------------- + +const ATTACK_RING_FLOATS = 3; + +const FADE_IN_MS = 200; +const FADE_OUT_MS = 300; + +// --------------------------------------------------------------------------- +// FxAttackRingPass +// --------------------------------------------------------------------------- + +export class FxAttackRingPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + + private program: WebGLProgram; + private uCamera: WebGLUniformLocation; + private uTilesPerPx: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uRingWidth: WebGLUniformLocation; + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + private ringCount = 0; + + private active: ActiveAttackRing[] = []; + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + + this.program = createProgram(gl, attackRingVertSrc, attackRingFragSrc); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTilesPerPx = gl.getUniformLocation(this.program, "uTilesPerPx")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uRingWidth = gl.getUniformLocation(this.program, "uRingWidth")!; + + const glBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer( + gl, + glBuf, + 8, + ATTACK_RING_FLOATS, + ); + + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, glBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0); + gl.vertexAttribDivisor(1, 1); + + gl.bindVertexArray(null); + } + + // ------------------------------------------------------------------------- + // Update + // ------------------------------------------------------------------------- + + update(rings: AttackRingInput[]): void { + const now = performance.now(); + const incoming = new Set(); + for (const r of rings) incoming.add(r.unitId); + + // Mark removed rings as fading out + for (const ar of this.active) { + if (!ar.fadingOut && !incoming.has(ar.unitId)) { + ar.fadingOut = true; + ar.transitionMs = now; + } + } + + // Add or refresh rings + for (const r of rings) { + const existing = this.active.find((a) => a.unitId === r.unitId); + if (existing) { + existing.x = r.x; + existing.y = r.y; + if (existing.fadingOut) { + existing.fadingOut = false; + existing.transitionMs = now; + } + } else { + this.active.push({ + unitId: r.unitId, + x: r.x, + y: r.y, + transitionMs: now, + fadingOut: false, + }); + } + } + } + + // ------------------------------------------------------------------------- + // Tick + // ------------------------------------------------------------------------- + + tick(): void { + if (this.active.length === 0) return; + const now = performance.now(); + + // Remove fully faded rings + for (let i = this.active.length - 1; i >= 0; i--) { + const ar = this.active[i]; + if (ar.fadingOut && now - ar.transitionMs >= FADE_OUT_MS) { + this.active[i] = this.active[this.active.length - 1]; + this.active.pop(); + } + } + + const count = this.active.length; + this.instanceBuf.ensureCapacity(count); + + const data = this.instanceBuf.float32; + for (let i = 0; i < count; i++) { + const ar = this.active[i]; + const elapsed = now - ar.transitionMs; + const alpha = ar.fadingOut + ? Math.max(0, 1 - elapsed / FADE_OUT_MS) + : Math.min(1, elapsed / FADE_IN_MS); + const off = i * ATTACK_RING_FLOATS; + data[off + 0] = ar.x; + data[off + 1] = ar.y; + data[off + 2] = alpha; + } + + this.ringCount = count; + } + + // ------------------------------------------------------------------------- + // Draw + // ------------------------------------------------------------------------- + + draw(cameraMatrix: Float32Array, zoom: number): void { + if (this.ringCount === 0) return; + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTilesPerPx, 1 / zoom); + gl.uniform1f(this.uTime, performance.now() / 1000); + gl.uniform1f(this.uRingWidth, this.settings.fx.shockwaveRingWidth); + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + this.ringCount * ATTACK_RING_FLOATS, + ); + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.ringCount); + } + + // ------------------------------------------------------------------------- + // Lifecycle + // ------------------------------------------------------------------------- + + clear(): void { + this.active.length = 0; + this.ringCount = 0; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts b/src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts new file mode 100644 index 0000000000..62d011986f --- /dev/null +++ b/src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts @@ -0,0 +1,191 @@ +/** + * FxShockwavePass — instanced procedural ring quads. + * + * Spawned alongside sprite FX for nuke and SAM interception events. + * Uses an SDF circle rendered in a unit quad, no texture required. + */ + +import { DynamicInstanceBuffer } from "../../dynamic-buffer"; +import type { RenderSettings } from "../../render-settings"; +import { createProgram } from "../../utils/gl-utils"; + +import shockwaveFragSrc from "../../shaders/fx/shockwave.frag.glsl?raw"; +import shockwaveVertSrc from "../../shaders/fx/shockwave.vert.glsl?raw"; + +// --------------------------------------------------------------------------- +// Active state +// --------------------------------------------------------------------------- + +interface ActiveShockwave { + x: number; + y: number; + startMs: number; + durationMs: number; + maxRadius: number; +} + +// --------------------------------------------------------------------------- +// Instance data layout: x, y, radius, alpha +// --------------------------------------------------------------------------- + +const SHOCKWAVE_FLOATS = 4; + +// --------------------------------------------------------------------------- +// FxShockwavePass +// --------------------------------------------------------------------------- + +export class FxShockwavePass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + + private program: WebGLProgram; + private uCamera: WebGLUniformLocation; + private uRingWidth: WebGLUniformLocation; + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + private shockwaveCount = 0; + + private active: ActiveShockwave[] = []; + private timeFn: () => number = () => performance.now(); + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + + this.program = createProgram(gl, shockwaveVertSrc, shockwaveFragSrc); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uRingWidth = gl.getUniformLocation(this.program, "uRingWidth")!; + + const glBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer( + gl, + glBuf, + 16, + SHOCKWAVE_FLOATS, + ); + + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, glBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0); + gl.vertexAttribDivisor(1, 1); + + gl.bindVertexArray(null); + } + + // ------------------------------------------------------------------------- + // Spawning + // ------------------------------------------------------------------------- + + pushNukeShockwave(x: number, y: number, nukeRadius: number): void { + const fx = this.settings.fx; + this.active.push({ + x, + y, + startMs: this.timeFn(), + durationMs: fx.nukeShockwaveDurationMs, + maxRadius: nukeRadius * fx.nukeShockwaveRadiusFactor, + }); + } + + pushSAMShockwave(x: number, y: number): void { + const fx = this.settings.fx; + this.active.push({ + x, + y, + startMs: this.timeFn(), + durationMs: fx.samShockwaveDurationMs, + maxRadius: fx.samShockwaveRadius, + }); + } + + // ------------------------------------------------------------------------- + // Tick + // ------------------------------------------------------------------------- + + tick(): void { + if (this.active.length === 0) return; + const now = this.timeFn(); + + for (let i = this.active.length - 1; i >= 0; i--) { + if (now - this.active[i].startMs >= this.active[i].durationMs) { + this.active[i] = this.active[this.active.length - 1]; + this.active.pop(); + } + } + + this.rebuildInstances(now); + } + + private rebuildInstances(now: number): void { + const count = this.active.length; + this.instanceBuf.ensureCapacity(count); + + const data = this.instanceBuf.float32; + for (let i = 0; i < count; i++) { + const sw = this.active[i]; + const t = (now - sw.startMs) / sw.durationMs; + const off = i * SHOCKWAVE_FLOATS; + data[off + 0] = sw.x; + data[off + 1] = sw.y; + data[off + 2] = t * sw.maxRadius; + data[off + 3] = 1 - t; + } + + this.shockwaveCount = count; + } + + // ------------------------------------------------------------------------- + // Draw + // ------------------------------------------------------------------------- + + draw(cameraMatrix: Float32Array): void { + if (this.shockwaveCount === 0) return; + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uRingWidth, this.settings.fx.shockwaveRingWidth); + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + this.shockwaveCount * SHOCKWAVE_FLOATS, + ); + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.shockwaveCount); + } + + // ------------------------------------------------------------------------- + // Lifecycle + // ------------------------------------------------------------------------- + + setTimeFn(fn: () => number): void { + this.timeFn = fn; + } + + clear(): void { + this.active.length = 0; + this.shockwaveCount = 0; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts b/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts new file mode 100644 index 0000000000..ecd61d0a68 --- /dev/null +++ b/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts @@ -0,0 +1,526 @@ +/** + * FxSpritePass — instanced textured quads sampling an animated sprite atlas. + * + * Manages: sprite FX state (explosions, dust, conquest, debris). + * Atlas layout: 12 horizontal sprite strips stacked vertically. + * Pre-built by generate-sprite-atlases.mjs. + */ + +import { MS_PER_TICK, NUKE_EXPLOSION_RADII } from "../../../game-constants"; +import type { ConquestFx, DeadUnitFx, RendererConfig } from "../../../types"; +import { + STRUCTURE_TYPES, + UT_SHELL, + UT_TRAIN, + UT_WARSHIP, +} from "../../../types"; +import { DynamicInstanceBuffer } from "../../dynamic-buffer"; +import type { RenderSettings } from "../../render-settings"; +import { createProgram, shaderSrc } from "../../utils/gl-utils"; + +import fxAtlasMeta from "../../assets/fx-atlas-meta.json"; +import fxAtlasUrl from "../../assets/fx-atlas.png?url"; + +import spriteFragSrc from "../../shaders/fx/sprite.frag.glsl?raw"; +import spriteVertSrc from "../../shaders/fx/sprite.vert.glsl?raw"; + +// --------------------------------------------------------------------------- +// FX type indices (atlas row) +// --------------------------------------------------------------------------- + +export const FX_NUKE = 0; +export const FX_SAM_EXPLOSION = 1; +export const FX_BUILDING_EXPLOSION = 2; +export const FX_UNIT_EXPLOSION = 3; +export const FX_MINI_EXPLOSION = 4; +export const FX_SINKING_SHIP = 5; +export const FX_MINI_FIRE = 6; +export const FX_MINI_SMOKE = 7; +export const FX_MINI_BIG_SMOKE = 8; +export const FX_MINI_SMOKE_FIRE = 9; +export const FX_DUST = 10; +export const FX_CONQUEST = 11; +const FX_TYPE_COUNT = 12; + +// --------------------------------------------------------------------------- +// FX sprite config (matches AnimatedSpriteLoader) +// --------------------------------------------------------------------------- + +interface FxTypeConfig { + frameWidth: number; + frameCount: number; + frameDurationMs: number; + looping: boolean; +} + +const FX_CONFIG: FxTypeConfig[] = [ + /* 0 Nuke */ { + frameWidth: 60, + frameCount: 9, + frameDurationMs: 70, + looping: false, + }, + /* 1 SAMExplosion */ { + frameWidth: 48, + frameCount: 9, + frameDurationMs: 70, + looping: false, + }, + /* 2 BuildingExpl */ { + frameWidth: 17, + frameCount: 10, + frameDurationMs: 70, + looping: false, + }, + /* 3 UnitExplosion */ { + frameWidth: 19, + frameCount: 4, + frameDurationMs: 70, + looping: false, + }, + /* 4 MiniExplosion */ { + frameWidth: 13, + frameCount: 4, + frameDurationMs: 70, + looping: false, + }, + /* 5 SinkingShip */ { + frameWidth: 16, + frameCount: 14, + frameDurationMs: 90, + looping: false, + }, + /* 6 MiniFire */ { + frameWidth: 7, + frameCount: 6, + frameDurationMs: 100, + looping: true, + }, + /* 7 MiniSmoke */ { + frameWidth: 11, + frameCount: 4, + frameDurationMs: 120, + looping: true, + }, + /* 8 MiniBigSmoke */ { + frameWidth: 24, + frameCount: 5, + frameDurationMs: 120, + looping: true, + }, + /* 9 MiniSmokeFire */ { + frameWidth: 24, + frameCount: 5, + frameDurationMs: 120, + looping: true, + }, + /* 10 Dust */ { + frameWidth: 9, + frameCount: 3, + frameDurationMs: 100, + looping: false, + }, + /* 11 Conquest */ { + frameWidth: 21, + frameCount: 10, + frameDurationMs: 90, + looping: false, + }, +]; + +// --------------------------------------------------------------------------- +// Nuke debris plan +// --------------------------------------------------------------------------- + +const DEBRIS_PLAN = [ + { type: FX_MINI_FIRE, radiusFactor: 1.0, density: 1 / 25 }, + { type: FX_MINI_SMOKE, radiusFactor: 1.0, density: 1 / 28 }, + { type: FX_MINI_BIG_SMOKE, radiusFactor: 0.9, density: 1 / 70 }, + { type: FX_MINI_SMOKE_FIRE, radiusFactor: 0.9, density: 1 / 70 }, +]; + +/** Deterministic float in [0,1) from an integer seed (mulberry32). */ +function seededRandom(seed: number): number { + seed = (seed + 0x6d2b79f5) | 0; + let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; +} + +// --------------------------------------------------------------------------- +// Active FX state +// --------------------------------------------------------------------------- + +interface ActiveFx { + x: number; + y: number; + fxType: number; + startMs: number; + lifetimeMs: number; + fadeIn: number; // fraction 0–1 (start of full alpha) + fadeOut: number; // fraction 0–1 (start of fade out) +} + +// --------------------------------------------------------------------------- +// Instance data layout +// --------------------------------------------------------------------------- + +const SPRITE_FLOATS = 4; // x, y, fxType, [frameIdx u8, alpha u8, pad, pad] +const SPRITE_BYTES = 16; + +// --------------------------------------------------------------------------- +// FxSpritePass +// --------------------------------------------------------------------------- + +export class FxSpritePass { + private gl: WebGL2RenderingContext; + private mapW: number; + private settings: RenderSettings; + + private program: WebGLProgram; + private uCamera: WebGLUniformLocation; + private uFxUV: WebGLUniformLocation; + private uFxWorld: WebGLUniformLocation; + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + private spriteCount = 0; + private atlasTex: WebGLTexture; + private atlasReady = false; + + private activeFx: ActiveFx[] = []; + private timeFn: () => number = () => performance.now(); + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + settings: RenderSettings, + ) { + this.gl = gl; + this.mapW = header.mapWidth; + this.settings = settings; + + this.program = createProgram( + gl, + shaderSrc(spriteVertSrc, { FX_TYPE_COUNT }), + spriteFragSrc, + ); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uFxUV = gl.getUniformLocation(this.program, "uFxUV")!; + this.uFxWorld = gl.getUniformLocation(this.program, "uFxWorld")!; + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uAtlas"), 0); + + // Placeholder atlas (1x1 transparent) + this.atlasTex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + 1, + 1, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + new Uint8Array([0, 0, 0, 0]), + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + // Instance buffer + const glBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer(gl, glBuf, 256, SPRITE_FLOATS); + + // VAO + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, glBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 3, gl.FLOAT, false, SPRITE_BYTES, 0); + gl.vertexAttribDivisor(1, 1); + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 2, gl.UNSIGNED_BYTE, false, SPRITE_BYTES, 12); + gl.vertexAttribDivisor(2, 1); + + gl.bindVertexArray(null); + + this.loadAtlas(); + } + + // ------------------------------------------------------------------------- + // Atlas loading + // ------------------------------------------------------------------------- + + private async loadAtlas(): Promise { + const img = new Image(); + img.src = fxAtlasUrl; + await img.decode(); + const gl = this.gl; + + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + const meta = fxAtlasMeta; + const uvData = new Float32Array(FX_TYPE_COUNT * 4); + const worldData = new Float32Array(FX_TYPE_COUNT * 4); + + for (let i = 0; i < FX_TYPE_COUNT; i++) { + const row = meta.rows[i]; + uvData[i * 4 + 0] = row.yOffset / meta.height; + uvData[i * 4 + 1] = row.height / meta.height; + uvData[i * 4 + 2] = row.worldWidth / meta.width; + uvData[i * 4 + 3] = 0; + worldData[i * 4 + 0] = row.worldWidth; + worldData[i * 4 + 1] = row.worldHeight; + worldData[i * 4 + 2] = 0; + worldData[i * 4 + 3] = 0; + } + + gl.useProgram(this.program); + gl.uniform4fv(this.uFxUV, uvData); + gl.uniform4fv(this.uFxWorld, worldData); + + this.atlasReady = true; + } + + // ------------------------------------------------------------------------- + // Spawning + // ------------------------------------------------------------------------- + + applyRailroadDust(tileRefs: number[]): void { + const now = this.timeFn(); + for (const ref of tileRefs) { + if (Math.random() > 0.33) continue; + const x = ref % this.mapW; + const y = (ref - x) / this.mapW; + this.pushFx(x, y, FX_DUST, now); + } + } + + applyConquestEvents(events: ConquestFx[]): void { + const now = this.timeFn(); + const fx = this.settings.fx; + for (const evt of events) { + const startMs = now - (evt.tickAge ?? 0) * MS_PER_TICK; + if (now - startMs >= fx.conquestLifetimeMs) continue; + this.activeFx.push({ + x: evt.x, + y: evt.y, + fxType: FX_CONQUEST, + startMs, + lifetimeMs: fx.conquestLifetimeMs, + fadeIn: fx.conquestFadeIn, + fadeOut: fx.conquestFadeOut, + }); + } + } + + /** + * Spawn sprite FX for a dead unit. Returns the nuke radius if a nuke + * exploded (so the orchestrator can also spawn a shockwave), or null. + */ + spawnFxForUnit(unit: DeadUnitFx, now: number): void { + const typeName = unit.unitType; + const x = unit.pos % this.mapW; + const y = (unit.pos - x) / this.mapW; + + const nukeRadius = NUKE_EXPLOSION_RADII[typeName]; + if (nukeRadius !== undefined) { + if (unit.reachedTarget) { + this.spawnNukeSprites(x, y, nukeRadius, now, unit.pos); + } else { + this.pushFx(x, y, FX_SAM_EXPLOSION, now); + } + return; + } + + if (typeName === UT_WARSHIP) { + this.pushFx(x, y, FX_UNIT_EXPLOSION, now); + this.pushFx(x, y, FX_SINKING_SHIP, now); + return; + } + + if (typeName === UT_SHELL && unit.reachedTarget) { + this.pushFx(x, y, FX_MINI_EXPLOSION, now); + return; + } + + if (typeName === UT_TRAIN && !unit.reachedTarget) { + this.pushFx(x, y, FX_MINI_EXPLOSION, now); + return; + } + + if (STRUCTURE_TYPES.has(typeName)) { + this.pushFx(x, y, FX_BUILDING_EXPLOSION, now); + } + } + + private spawnNukeSprites( + x: number, + y: number, + radius: number, + now: number, + pos: number, + ): void { + this.pushFx(x, y, FX_NUKE, now); + + let debrisIdx = 0; + for (const { type, radiusFactor, density } of DEBRIS_PLAN) { + const count = Math.max(0, Math.floor(radius * density)); + const r = radius * radiusFactor; + for (let i = 0; i < count; i++) { + const seed = pos * 997 + debrisIdx++; + const angle = seededRandom(seed) * Math.PI * 2; + const dist = seededRandom(seed + 0x10000) * (r / 2); + const dx = Math.floor(Math.cos(angle) * dist); + const dy = Math.floor(Math.sin(angle) * dist); + this.pushDebris(x + dx, y + dy, type, now); + } + } + } + + pushFx(x: number, y: number, fxType: number, now: number): void { + const cfg = FX_CONFIG[fxType]; + this.activeFx.push({ + x, + y, + fxType, + startMs: now, + lifetimeMs: cfg.frameDurationMs * cfg.frameCount, + fadeIn: 0, + fadeOut: 1, + }); + } + + private pushDebris(x: number, y: number, fxType: number, now: number): void { + const fx = this.settings.fx; + this.activeFx.push({ + x, + y, + fxType, + startMs: now, + lifetimeMs: fx.debrisLifetimeMs, + fadeIn: fx.debrisFadeIn, + fadeOut: fx.debrisFadeOut, + }); + } + + // ------------------------------------------------------------------------- + // Tick + // ------------------------------------------------------------------------- + + tick(): void { + if (this.activeFx.length === 0) return; + const now = this.timeFn(); + + for (let i = this.activeFx.length - 1; i >= 0; i--) { + if (now - this.activeFx[i].startMs >= this.activeFx[i].lifetimeMs) { + this.activeFx[i] = this.activeFx[this.activeFx.length - 1]; + this.activeFx.pop(); + } + } + + this.rebuildInstances(now); + } + + private rebuildInstances(now: number): void { + const count = this.activeFx.length; + this.instanceBuf.ensureCapacity(count); + + for (let i = 0; i < count; i++) { + const fx = this.activeFx[i]; + const cfg = FX_CONFIG[fx.fxType]; + const elapsed = now - fx.startMs; + + let frameIdx: number; + if (cfg.looping) { + const cycle = cfg.frameDurationMs * cfg.frameCount; + frameIdx = Math.floor((elapsed % cycle) / cfg.frameDurationMs); + } else { + frameIdx = Math.min( + Math.floor(elapsed / cfg.frameDurationMs), + cfg.frameCount - 1, + ); + } + + let alpha = 255; + if (fx.fadeIn > 0 || fx.fadeOut < 1) { + const t = elapsed / fx.lifetimeMs; + if (t < fx.fadeIn) { + alpha = Math.floor((t / fx.fadeIn) * 255); + } else if (t > fx.fadeOut) { + alpha = Math.floor(((1 - t) / (1 - fx.fadeOut)) * 255); + } + } + + const off = i * SPRITE_FLOATS; + this.instanceBuf.float32[off + 0] = fx.x; + this.instanceBuf.float32[off + 1] = fx.y; + this.instanceBuf.float32[off + 2] = fx.fxType; + const byteOff = i * SPRITE_BYTES; + this.instanceBuf.uint8[byteOff + 12] = frameIdx; + this.instanceBuf.uint8[byteOff + 13] = alpha; + } + + this.spriteCount = count; + } + + // ------------------------------------------------------------------------- + // Draw + // ------------------------------------------------------------------------- + + draw(cameraMatrix: Float32Array): void { + if (this.spriteCount === 0 || !this.atlasReady) return; + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + this.spriteCount * SPRITE_FLOATS, + ); + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.spriteCount); + } + + // ------------------------------------------------------------------------- + // Lifecycle + // ------------------------------------------------------------------------- + + setTimeFn(fn: () => number): void { + this.timeFn = fn; + } + + clear(): void { + this.activeFx.length = 0; + this.spriteCount = 0; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + gl.deleteVertexArray(this.vao); + gl.deleteTexture(this.atlasTex); + } +} diff --git a/src/client/render/gl/passes/fx-pass/index.ts b/src/client/render/gl/passes/fx-pass/index.ts new file mode 100644 index 0000000000..b46a8b7bf4 --- /dev/null +++ b/src/client/render/gl/passes/fx-pass/index.ts @@ -0,0 +1,126 @@ +/** + * FxPass — orchestrates three independent GPU effect sub-passes: + * 1. FxSpritePass — animated sprite atlas (explosions, dust, conquest) + * 2. FxShockwavePass — procedural rings for nuke/SAM events + * 3. FxAttackRingPass — persistent rings at transport ship targets + * + * Spawn events that produce both a sprite and a shockwave (nukes, SAM + * interceptions) are coordinated here so each sub-pass stays self-contained. + */ + +import { MS_PER_TICK, NUKE_EXPLOSION_RADII } from "../../../game-constants"; +import type { + AttackRingInput, + ConquestFx, + DeadUnitFx, + RendererConfig, +} from "../../../types"; +import type { RenderSettings } from "../../render-settings"; +import { FxAttackRingPass } from "./fx-attack-ring-pass"; +import { FxShockwavePass } from "./fx-shockwave-pass"; +import { FxSpritePass } from "./fx-sprite-pass"; + +export type { AttackRingInput } from "../../../types"; + +export class FxPass { + private spritePass: FxSpritePass; + private shockwavePass: FxShockwavePass; + private attackRingPass: FxAttackRingPass; + private mapW: number; + private timeFn: () => number = () => performance.now(); + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + settings: RenderSettings, + ) { + this.mapW = header.mapWidth; + this.spritePass = new FxSpritePass(gl, header, settings); + this.shockwavePass = new FxShockwavePass(gl, settings); + this.attackRingPass = new FxAttackRingPass(gl, settings); + } + + // ------------------------------------------------------------------------- + // Spawning — coordinated across sub-passes + // ------------------------------------------------------------------------- + + applyDeadUnits(deadUnits: DeadUnitFx[]): void { + const now = this.timeFn(); + for (const unit of deadUnits) { + const startMs = now - (unit.tickAge ?? 0) * MS_PER_TICK; + this.spawnUnit(unit, startMs); + } + } + + private spawnUnit(unit: DeadUnitFx, now: number): void { + const typeName = unit.unitType; + const x = unit.pos % this.mapW; + const y = (unit.pos - x) / this.mapW; + + const nukeRadius = NUKE_EXPLOSION_RADII[typeName]; + if (nukeRadius !== undefined) { + if (unit.reachedTarget) { + this.spritePass.spawnFxForUnit(unit, now); + this.shockwavePass.pushNukeShockwave(x, y, nukeRadius); + } else { + // SAM interception: sprite pass handles the SAM explosion sprite + this.spritePass.spawnFxForUnit(unit, now); + this.shockwavePass.pushSAMShockwave(x, y); + } + return; + } + + // All other units: sprite-only effects + this.spritePass.spawnFxForUnit(unit, now); + } + + applyRailroadDust(tileRefs: number[]): void { + this.spritePass.applyRailroadDust(tileRefs); + } + + applyConquestEvents(events: ConquestFx[]): void { + this.spritePass.applyConquestEvents(events); + } + + updateAttackRings(rings: AttackRingInput[]): void { + this.attackRingPass.update(rings); + } + + // ------------------------------------------------------------------------- + // Per-frame + // ------------------------------------------------------------------------- + + tick(): void { + this.spritePass.tick(); + this.shockwavePass.tick(); + this.attackRingPass.tick(); + } + + draw(cameraMatrix: Float32Array, zoom: number): void { + this.spritePass.draw(cameraMatrix); + this.shockwavePass.draw(cameraMatrix); + this.attackRingPass.draw(cameraMatrix, zoom); + } + + // ------------------------------------------------------------------------- + // Lifecycle + // ------------------------------------------------------------------------- + + setTimeFn(fn: () => number): void { + this.timeFn = fn; + this.spritePass.setTimeFn(fn); + this.shockwavePass.setTimeFn(fn); + } + + clear(): void { + this.spritePass.clear(); + this.shockwavePass.clear(); + this.attackRingPass.clear(); + } + + dispose(): void { + this.spritePass.dispose(); + this.shockwavePass.dispose(); + this.attackRingPass.dispose(); + } +} diff --git a/src/client/render/gl/passes/lightmap-pass.ts b/src/client/render/gl/passes/lightmap-pass.ts new file mode 100644 index 0000000000..8107abe6e5 --- /dev/null +++ b/src/client/render/gl/passes/lightmap-pass.ts @@ -0,0 +1,206 @@ +/** + * LightmapPass — orchestrator: point lights + fallout lights → blur → final texture. + * + * Owns the quarter-resolution lightmap ping-pong FBOs and the blur shader. + * Delegates light rendering to PointLightPass and FalloutLightPass. + */ + +import type { RenderSettings } from "../render-settings"; +import { createFullscreenQuad, createProgram } from "../utils/gl-utils"; +import type { FalloutLightPass } from "./fallout-light-pass"; +import type { PointLightPass } from "./point-light-pass"; + +import blurFragSrc from "../shaders/shared/blur.frag.glsl?raw"; +import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw"; + +export class LightmapPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + + private pointLightPass: PointLightPass; + private falloutLightPass: FalloutLightPass; + + // Blur program + private blurProg: WebGLProgram; + private uBlurDir: WebGLUniformLocation; + + // Quarter-res lightmap ping-pong + private lightFboA: WebGLFramebuffer; + private lightFboB: WebGLFramebuffer; + private lightTexA: WebGLTexture; + private lightTexB: WebGLTexture; + private lightW = 0; + private lightH = 0; + + // Geometry + private quadVao: WebGLVertexArrayObject; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + pointLightPass: PointLightPass, + falloutLightPass: FalloutLightPass, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.pointLightPass = pointLightPass; + this.falloutLightPass = falloutLightPass; + + // Blur program + this.blurProg = createProgram(gl, fullscreenVertSrc, blurFragSrc); + this.uBlurDir = gl.getUniformLocation(this.blurProg, "uDir")!; + gl.useProgram(this.blurProg); + gl.uniform1i(gl.getUniformLocation(this.blurProg, "uTex"), 0); + + // Lightmap FBOs (1×1 placeholder, resized lazily) + this.lightTexA = this.createRGBA8Tex(); + this.lightTexB = this.createRGBA8Tex(); + this.lightFboA = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.lightFboA); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.lightTexA, + 0, + ); + this.lightFboB = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.lightFboB); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + this.lightTexB, + 0, + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + this.quadVao = createFullscreenQuad(gl); + } + + private createRGBA8Tex(): WebGLTexture { + const gl = this.gl; + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + 1, + 1, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + return tex; + } + + private ensureLightSize(w: number, h: number): void { + if (w === this.lightW && h === this.lightH) return; + this.lightW = w; + this.lightH = h; + const gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, this.lightTexA); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + w, + h, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + gl.bindTexture(gl.TEXTURE_2D, this.lightTexB); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + w, + h, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + } + + /** Generate the lightmap and return the final blurred texture. */ + draw( + cameraMatrix: Float32Array, + sceneW: number, + sceneH: number, + ): WebGLTexture { + const gl = this.gl; + const lw = Math.max(1, sceneW >> 1); + const lh = Math.max(1, sceneH >> 1); + this.ensureLightSize(lw, lh); + + // --- 1. Point lights → FBO A (additive) --- + gl.bindFramebuffer(gl.FRAMEBUFFER, this.lightFboA); + gl.viewport(0, 0, lw, lh); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE); // additive + + this.pointLightPass.draw(cameraMatrix); + + // --- 2. Fallout light → extract at tile res, composite into FBO A (additive) --- + this.falloutLightPass.draw(cameraMatrix, this.lightFboA, lw, lh); + + // --- 3. Blur: 2 iterations separable H+V Gaussian --- + const zoom = Math.abs(cameraMatrix[0]); + const mapSize = Math.max(this.mapW, this.mapH); + const blurScale = Math.min( + (zoom * mapSize) / this.settings.dayNight.blurZoomDivisor, + 1.0, + ); + + gl.disable(gl.BLEND); + gl.useProgram(this.blurProg); + gl.bindVertexArray(this.quadVao); + + for (let iter = 0; iter < 2; iter++) { + gl.bindFramebuffer(gl.FRAMEBUFFER, this.lightFboB); + gl.viewport(0, 0, lw, lh); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(this.uBlurDir, blurScale / lw, 0); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.lightTexA); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.lightFboA); + gl.viewport(0, 0, lw, lh); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(this.uBlurDir, 0, blurScale / lh); + gl.bindTexture(gl.TEXTURE_2D, this.lightTexB); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + return this.lightTexA; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.blurProg); + gl.deleteFramebuffer(this.lightFboA); + gl.deleteFramebuffer(this.lightFboB); + gl.deleteTexture(this.lightTexA); + gl.deleteTexture(this.lightTexB); + gl.deleteVertexArray(this.quadVao); + // pointLightPass and falloutLightPass disposed by renderer + } +} diff --git a/src/client/render/gl/passes/move-indicator-pass.ts b/src/client/render/gl/passes/move-indicator-pass.ts new file mode 100644 index 0000000000..42c8ac30ba --- /dev/null +++ b/src/client/render/gl/passes/move-indicator-pass.ts @@ -0,0 +1,115 @@ +/** + * MoveIndicatorPass — converging chevron animation at a warship's + * move-target location. Matches the upstream game's MoveIndicatorUI + * but rendered via SDF in a fragment shader. + */ + +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; + +import fragSrc from "../shaders/move-indicator/move-indicator.frag.glsl?raw"; +import vertSrc from "../shaders/move-indicator/move-indicator.vert.glsl?raw"; + +export class MoveIndicatorPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + + private uCamera: WebGLUniformLocation; + private uCenter: WebGLUniformLocation; + private uElapsed: WebGLUniformLocation; + private uColor: WebGLUniformLocation; + private uPxPerTile: WebGLUniformLocation; + private uStartRadius: WebGLUniformLocation; + private uChevronSize: WebGLUniformLocation; + private uLineWidth: WebGLUniformLocation; + private uDuration: WebGLUniformLocation; + private uConverge: WebGLUniformLocation; + + private active = false; + private centerX = 0; + private centerY = 0; + private colorR = 1; + private colorG = 0; + private colorB = 0; + private startTime = 0; + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + this.program = createProgram(gl, vertSrc, fragSrc); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uCenter = gl.getUniformLocation(this.program, "uCenter")!; + this.uElapsed = gl.getUniformLocation(this.program, "uElapsed")!; + this.uColor = gl.getUniformLocation(this.program, "uColor")!; + this.uPxPerTile = gl.getUniformLocation(this.program, "uPxPerTile")!; + this.uStartRadius = gl.getUniformLocation(this.program, "uStartRadius")!; + this.uChevronSize = gl.getUniformLocation(this.program, "uChevronSize")!; + this.uLineWidth = gl.getUniformLocation(this.program, "uLineWidth")!; + this.uDuration = gl.getUniformLocation(this.program, "uDuration")!; + this.uConverge = gl.getUniformLocation(this.program, "uConverge")!; + + // Unit quad [0,1] + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + const buf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + } + + /** + * Trigger the move indicator at world tile (x, y) with player color. + * Each call replaces the previous indicator. + */ + show(x: number, y: number, r: number, g: number, b: number): void { + this.active = true; + this.centerX = x; + this.centerY = y; + this.colorR = r; + this.colorG = g; + this.colorB = b; + this.startTime = performance.now(); + } + + draw(cameraMatrix: Float32Array, zoom: number): void { + if (!this.active) return; + + const s = this.settings.moveIndicator; + const elapsed = performance.now() - this.startTime; + if (elapsed >= s.duration) { + this.active = false; + return; + } + + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uCenter, this.centerX, this.centerY); + gl.uniform1f(this.uElapsed, elapsed); + gl.uniform3f(this.uColor, this.colorR, this.colorG, this.colorB); + gl.uniform1f(this.uPxPerTile, zoom); + gl.uniform1f(this.uStartRadius, s.startRadius); + gl.uniform1f(this.uChevronSize, s.chevronSize); + gl.uniform1f(this.uLineWidth, s.lineWidth); + gl.uniform1f(this.uDuration, s.duration); + gl.uniform1f(this.uConverge, s.converge); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/name-pass/atlas-data.ts b/src/client/render/gl/passes/name-pass/atlas-data.ts new file mode 100644 index 0000000000..73ef508c36 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/atlas-data.ts @@ -0,0 +1,86 @@ +/** + * Atlas data parsing — extracts font metrics, glyph lookup tables, + * kerning data, and icon atlas index maps from static JSON assets. + */ + +import emojiAtlasMeta from "../../assets/emoji-atlas-meta.json"; +import flagAtlasMeta from "../../assets/flag-atlas-meta.json"; +import atlasData from "../../assets/msdf-atlas.json"; +import type { BMChar, BMKerning, ParsedAtlas } from "./types"; +import { CHAR_RANGE } from "./types"; + +// --------------------------------------------------------------------------- +// Atlas parsing +// --------------------------------------------------------------------------- + +export function parseAtlasData(): ParsedAtlas { + return { + fontSize: atlasData.info.size, + base: atlasData.common.base, + scaleW: atlasData.common.scaleW, + scaleH: atlasData.common.scaleH, + distanceRange: (atlasData as any).distanceField?.distanceRange ?? 4, + chars: atlasData.chars as BMChar[], + kernings: (atlasData.kernings ?? []) as BMKerning[], + }; +} + +// --------------------------------------------------------------------------- +// CPU-side glyph lookup tables +// --------------------------------------------------------------------------- + +export interface GlyphTables { + advance: Float32Array; // [CHAR_RANGE] — xadvance per char ID + xOffset: Float32Array; // [CHAR_RANGE] — xoffset (left bearing) per char ID + visW: Float32Array; // [CHAR_RANGE] — visible glyph width per char ID +} + +export function buildGlyphTables(chars: BMChar[]): GlyphTables { + const advance = new Float32Array(CHAR_RANGE); + const xOffset = new Float32Array(CHAR_RANGE); + const visW = new Float32Array(CHAR_RANGE); + for (const ch of chars) { + if (ch.id < CHAR_RANGE) { + advance[ch.id] = ch.xadvance; + xOffset[ch.id] = ch.xoffset; + visW[ch.id] = ch.width; + } + } + return { advance, xOffset, visW }; +} + +// --------------------------------------------------------------------------- +// Kerning table (amounts are small integers: typically -7 to +4) +// --------------------------------------------------------------------------- + +export function buildKernTable(kernings: BMKerning[]): Int8Array { + const table = new Int8Array(CHAR_RANGE * CHAR_RANGE); + for (const k of kernings) { + if (k.first < CHAR_RANGE && k.second < CHAR_RANGE) { + table[k.first * CHAR_RANGE + k.second] = k.amount; + } + } + return table; +} + +// --------------------------------------------------------------------------- +// Icon atlas lookups +// --------------------------------------------------------------------------- + +export function buildFlagLookup(): Map { + const map = new Map(); + const meta = flagAtlasMeta as { flags: Record }; + for (const [code, idx] of Object.entries(meta.flags)) { + map.set(code, idx); + } + return map; +} + +export function buildEmojiLookup(): Map { + const map = new Map(); + const meta = emojiAtlasMeta as { emojis: Record }; + for (const [ch, idx] of Object.entries(meta.emojis)) { + map.set(ch, idx); + } + return map; +} diff --git a/src/client/render/gl/passes/name-pass/data-textures.ts b/src/client/render/gl/passes/name-pass/data-textures.ts new file mode 100644 index 0000000000..b97ceb5073 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/data-textures.ts @@ -0,0 +1,89 @@ +/** + * Data texture factories for the NamePass subsystem. + * Uses createTexture2D from gl-utils to eliminate boilerplate. + */ + +import { createTexture2D } from "../../utils/gl-utils"; +import type { ParsedAtlas } from "./types"; +import { CHAR_RANGE, LINES_PER_PLAYER, MAX_CHARS } from "./types"; + +/** Glyph metrics: CHAR_RANGE x 2, RGBA32F. Static — uploaded once. */ +export function buildGlyphMetricsTex( + gl: WebGL2RenderingContext, + atlas: ParsedAtlas, +): WebGLTexture { + const data = new Float32Array(CHAR_RANGE * 2 * 4); + + for (const ch of atlas.chars) { + if (ch.id >= CHAR_RANGE) continue; + // Row 0: xadvance, xoffset, yoffset, width + const r0 = ch.id * 4; + data[r0 + 0] = ch.xadvance; + data[r0 + 1] = ch.xoffset; + data[r0 + 2] = ch.yoffset; + data[r0 + 3] = ch.width; + // Row 1: height, atlasU0, atlasV0, atlasU1 + const r1 = (CHAR_RANGE + ch.id) * 4; + data[r1 + 0] = ch.height; + data[r1 + 1] = ch.x / atlas.scaleW; + data[r1 + 2] = ch.y / atlas.scaleH; + data[r1 + 3] = (ch.x + ch.width) / atlas.scaleW; + // v1 is computed in shader as v0 + height/scaleH + } + + return createTexture2D(gl, { + width: CHAR_RANGE, + height: 2, + internalFormat: gl.RGBA32F, + format: gl.RGBA, + type: gl.FLOAT, + data, + }); +} + +/** Cursor positions: MAX_CHARS x (maxPlayers * LINES_PER_PLAYER), R32F. Dynamic. */ +export function buildCursorTex( + gl: WebGL2RenderingContext, + maxPlayers: number, +): WebGLTexture { + const height = maxPlayers * LINES_PER_PLAYER; + return createTexture2D(gl, { + width: MAX_CHARS, + height, + internalFormat: gl.R32F, + format: gl.RED, + type: gl.FLOAT, + data: new Float32Array(MAX_CHARS * height), + }); +} + +/** String data: MAX_CHARS x (maxPlayers * LINES_PER_PLAYER), R8UI. Dynamic. */ +export function buildStringTex( + gl: WebGL2RenderingContext, + maxPlayers: number, +): WebGLTexture { + const height = maxPlayers * LINES_PER_PLAYER; + return createTexture2D(gl, { + width: MAX_CHARS, + height, + internalFormat: gl.R8UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_BYTE, + data: new Uint8Array(MAX_CHARS * height), + }); +} + +/** Player data: 8 x maxPlayers, RGBA32F. Dynamic. */ +export function buildPlayerDataTex( + gl: WebGL2RenderingContext, + maxPlayers: number, +): WebGLTexture { + return createTexture2D(gl, { + width: 8, + height: maxPlayers, + internalFormat: gl.RGBA32F, + format: gl.RGBA, + type: gl.FLOAT, + data: new Float32Array(8 * maxPlayers * 4), + }); +} diff --git a/src/client/render/gl/passes/name-pass/debug-program.ts b/src/client/render/gl/passes/name-pass/debug-program.ts new file mode 100644 index 0000000000..71eab14d68 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/debug-program.ts @@ -0,0 +1,91 @@ +/** + * DebugProgram — wireframe bounding boxes for name/flag layout debugging. + * + * Owns: shader program, uniform locations. + * The shared playerDataTex is passed in but not owned/deleted. + */ + +import flagAtlasMeta from "../../assets/flag-atlas-meta.json"; +import type { RenderSettings } from "../../render-settings"; +import debugBoxFragSrc from "../../shaders/name/debug-box.frag.glsl?raw"; +import debugBoxVertSrc from "../../shaders/name/debug-box.vert.glsl?raw"; +import { createProgram } from "../../utils/gl-utils"; +import type { ParsedAtlas } from "./types"; + +export class DebugProgram { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private playerDataTex: WebGLTexture; + private maxPlayers: number; + + private uCamera: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uLerpSpeed: WebGLUniformLocation; + private uCullThreshold: WebGLUniformLocation; + private uNameScaleFactor: WebGLUniformLocation; + private uNameScaleCap: WebGLUniformLocation; + + constructor( + gl: WebGL2RenderingContext, + atlas: ParsedAtlas, + playerDataTex: WebGLTexture, + maxPlayers: number, + ) { + this.gl = gl; + this.playerDataTex = playerDataTex; + this.maxPlayers = maxPlayers; + + const fm = flagAtlasMeta as any; + this.program = createProgram(gl, debugBoxVertSrc, debugBoxFragSrc); + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uPlayerData"), 0); + gl.uniform1f( + gl.getUniformLocation(this.program, "uFontSize")!, + atlas.fontSize, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uFontBase")!, atlas.base); + gl.uniform1f(gl.getUniformLocation(this.program, "uFlagCellW")!, fm.cellW); + gl.uniform1f(gl.getUniformLocation(this.program, "uFlagCellH")!, fm.cellH); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uLerpSpeed = gl.getUniformLocation(this.program, "uLerpSpeed")!; + this.uCullThreshold = gl.getUniformLocation( + this.program, + "uCullThreshold", + )!; + this.uNameScaleFactor = gl.getUniformLocation( + this.program, + "uNameScaleFactor", + )!; + this.uNameScaleCap = gl.getUniformLocation(this.program, "uNameScaleCap")!; + } + + draw( + cameraMatrix: Float32Array, + settings: RenderSettings, + vao: WebGLVertexArrayObject, + ): void { + const gl = this.gl; + const ns = settings.name; + gl.useProgram(this.program); + + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTime, performance.now() / 1000); + gl.uniform1f(this.uLerpSpeed, ns.lerpSpeed); + gl.uniform1f(this.uCullThreshold, ns.cullThreshold); + gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor); + gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.playerDataTex); + + gl.bindVertexArray(vao); + // 3 instances per player: name box, flag box, center dot + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.maxPlayers * 3); + } + + dispose(): void { + this.gl.deleteProgram(this.program); + } +} diff --git a/src/client/render/gl/passes/name-pass/icon-program.ts b/src/client/render/gl/passes/name-pass/icon-program.ts new file mode 100644 index 0000000000..fb04f63537 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/icon-program.ts @@ -0,0 +1,186 @@ +/** + * IconProgram — instanced flag + emoji icons beside player names. + * + * Owns: shader program, uniform locations, flag atlas + emoji atlas textures. + * The shared playerDataTex is passed in but not owned/deleted. + */ + +import emojiAtlasMeta from "../../assets/emoji-atlas-meta.json"; +import emojiAtlasUrl from "../../assets/emoji-atlas.png?url"; +import flagAtlasMeta from "../../assets/flag-atlas-meta.json"; +import flagAtlasUrl from "../../assets/flag-atlas.png?url"; +import type { RenderSettings } from "../../render-settings"; +import iconFragSrc from "../../shaders/name/icon.frag.glsl?raw"; +import iconVertSrc from "../../shaders/name/icon.vert.glsl?raw"; +import { createProgram } from "../../utils/gl-utils"; +import type { ParsedAtlas } from "./types"; + +export class IconProgram { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private playerDataTex: WebGLTexture; + private maxPlayers: number; + + private flagAtlasTex: WebGLTexture | null = null; + private emojiAtlasTex: WebGLTexture | null = null; + private iconsReady = false; + + // Dynamic uniform locations + private uCamera: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uLerpSpeed: WebGLUniformLocation; + private uCullThreshold: WebGLUniformLocation; + private uNameScaleFactor: WebGLUniformLocation; + private uNameScaleCap: WebGLUniformLocation; + private uEmojiRowOffset: WebGLUniformLocation; + + constructor( + gl: WebGL2RenderingContext, + atlas: ParsedAtlas, + playerDataTex: WebGLTexture, + maxPlayers: number, + ) { + this.gl = gl; + this.playerDataTex = playerDataTex; + this.maxPlayers = maxPlayers; + + this.program = createProgram(gl, iconVertSrc, iconFragSrc); + gl.useProgram(this.program); + + // Texture unit bindings + gl.uniform1i(gl.getUniformLocation(this.program, "uPlayerData"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uFlagAtlas"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uEmojiAtlas"), 2); + + // Static uniforms from atlas metadata + const fm = flagAtlasMeta as any; + const em = emojiAtlasMeta as any; + gl.uniform1f( + gl.getUniformLocation(this.program, "uFontSize")!, + atlas.fontSize, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uFontBase")!, atlas.base); + gl.uniform1f(gl.getUniformLocation(this.program, "uFlagCellW")!, fm.cellW); + gl.uniform1f(gl.getUniformLocation(this.program, "uFlagCellH")!, fm.cellH); + gl.uniform1f(gl.getUniformLocation(this.program, "uFlagCols")!, fm.cols); + gl.uniform1f(gl.getUniformLocation(this.program, "uFlagAtlasW")!, fm.width); + gl.uniform1f( + gl.getUniformLocation(this.program, "uFlagAtlasH")!, + fm.height, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uEmojiCell")!, + em.cellSize, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uEmojiCols")!, em.cols); + gl.uniform1f( + gl.getUniformLocation(this.program, "uEmojiAtlasW")!, + em.width, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uEmojiAtlasH")!, + em.height, + ); + + // Dynamic uniform locations + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uLerpSpeed = gl.getUniformLocation(this.program, "uLerpSpeed")!; + this.uCullThreshold = gl.getUniformLocation( + this.program, + "uCullThreshold", + )!; + this.uNameScaleFactor = gl.getUniformLocation( + this.program, + "uNameScaleFactor", + )!; + this.uNameScaleCap = gl.getUniformLocation(this.program, "uNameScaleCap")!; + this.uEmojiRowOffset = gl.getUniformLocation( + this.program, + "uEmojiRowOffset", + )!; + + this.loadAtlases(); + } + + get ready(): boolean { + return this.iconsReady; + } + + private loadAtlases(): void { + const gl = this.gl; + const load = (url: string, cb: (tex: WebGLTexture) => void) => { + const img = new Image(); + img.onload = () => { + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + gl.LINEAR_MIPMAP_LINEAR, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + gl.RGBA, + gl.UNSIGNED_BYTE, + img, + ); + gl.generateMipmap(gl.TEXTURE_2D); + cb(tex); + }; + img.src = url; + }; + load(flagAtlasUrl, (tex) => { + this.flagAtlasTex = tex; + this.iconsReady = + this.flagAtlasTex !== null && this.emojiAtlasTex !== null; + }); + load(emojiAtlasUrl, (tex) => { + this.emojiAtlasTex = tex; + this.iconsReady = + this.flagAtlasTex !== null && this.emojiAtlasTex !== null; + }); + } + + draw( + cameraMatrix: Float32Array, + settings: RenderSettings, + vao: WebGLVertexArrayObject, + ): void { + if (!this.iconsReady) return; + + const gl = this.gl; + const ns = settings.name; + gl.useProgram(this.program); + + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTime, performance.now() / 1000); + gl.uniform1f(this.uLerpSpeed, ns.lerpSpeed); + gl.uniform1f(this.uCullThreshold, ns.cullThreshold); + gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor); + gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap); + gl.uniform1f(this.uEmojiRowOffset, ns.emojiRowOffset); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.playerDataTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.flagAtlasTex!); + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.emojiAtlasTex!); + + gl.bindVertexArray(vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.maxPlayers * 2); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + if (this.flagAtlasTex) gl.deleteTexture(this.flagAtlasTex); + if (this.emojiAtlasTex) gl.deleteTexture(this.emojiAtlasTex); + } +} diff --git a/src/client/render/gl/passes/name-pass/index.ts b/src/client/render/gl/passes/name-pass/index.ts new file mode 100644 index 0000000000..3f011452db --- /dev/null +++ b/src/client/render/gl/passes/name-pass/index.ts @@ -0,0 +1,573 @@ +/** + * NamePass — GPU-rendered player names + troop counts using MSDF text. + * + * All text layout, interpolation, and sizing runs on the GPU via instanced + * rendering. CPU cost per frame is effectively zero: one uniform update and + * one instanced draw call. Data changes (position/size targets, troop counts) + * are pushed as tiny texture sub-updates. + * + * Submodules: + * - text-program — MSDF text shader (names + troop counts) + * - icon-program — instanced flag + emoji icons + * - debug-program — wireframe bounding boxes for layout debugging + * - atlas-data — font/atlas parsing + glyph lookup tables + * - text-layout — pure CPU text shaping (cursor positions) + * - data-textures — GL data texture factories + * - types — shared interfaces + constants + */ + +import type { + NameEntry, + PlayerState, + PlayerStatic, + PlayerStatusData, + RendererConfig, +} from "../../../types"; +import { PlayerTypeEnum } from "../../../types"; +import type { RenderSettings } from "../../render-settings"; +import { createFullscreenQuad } from "../../utils/gl-utils"; + +import type { GlyphTables } from "./atlas-data"; +import { + buildEmojiLookup, + buildFlagLookup, + buildGlyphTables, + buildKernTable, + parseAtlasData, +} from "./atlas-data"; +import { + buildCursorTex, + buildGlyphMetricsTex, + buildPlayerDataTex, + buildStringTex, +} from "./data-textures"; +import { DebugProgram } from "./debug-program"; +import { IconProgram } from "./icon-program"; +import { StatusIconProgram } from "./status-icon-program"; +import { formatTroops, layoutString } from "./text-layout"; +import { TextProgram } from "./text-program"; +import type { PlayerSlot } from "./types"; +import { LINES_PER_PLAYER, MAX_CHARS } from "./types"; + +export class NamePass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + + // Shared geometry + private vao: WebGLVertexArrayObject; + + // Shared data textures + private glyphMetricsTex: WebGLTexture; + private cursorTex: WebGLTexture; + private stringTex: WebGLTexture; + private playerDataTex: WebGLTexture; + + // Sub-programs + private textProgram: TextProgram; + private iconProgram: IconProgram; + private statusIconProgram: StatusIconProgram; + private debugProgram: DebugProgram; + + // Atlas + glyph data + private glyph: GlyphTables; + private kernTable: Int8Array; + + // Player management + private playerByID: Map; + private smallIDToPlayerID: Map; + private slots: Map = new Map(); + private maxPlayers: number; + private playerColors: Map = new Map(); + private flagCodeToIndex: Map; + private emojiCharToIndex: Map; + + // CPU-side mirrors — batched upload in draw() + private cpuPlayerData: Float32Array; + private cpuStringData: Uint8Array; + private cpuCursorData: Float32Array; + private playerDataDirty = false; + private stringDataDirty = false; + private cursorDataDirty = false; + + // Reusable buffers for text layout + private stringRow: Uint8Array; + private cursorRow: Float32Array; + + // Reusable per-tick lookup maps (avoid allocation + GC) + private alivePlayerIDs = new Set(); + private troopsByPlayerID = new Map(); + private playerStateByID = new Map(); + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + paletteData: Float32Array, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.maxPlayers = header.maxPlayers ?? header.players.length; + + // Parse atlas + build CPU lookup tables + const atlas = parseAtlasData(); + this.glyph = buildGlyphTables(atlas.chars); + this.kernTable = buildKernTable(atlas.kernings); + this.flagCodeToIndex = buildFlagLookup(); + this.emojiCharToIndex = buildEmojiLookup(); + + // Build player lookups and extract territory colors from palette + this.playerByID = new Map(); + this.smallIDToPlayerID = new Map(); + for (const p of header.players) { + this.playerByID.set(p.id, p); + this.smallIDToPlayerID.set(p.smallID, p.id); + const off = p.smallID * 4; + this.playerColors.set(p.id, [ + paletteData[off], + paletteData[off + 1], + paletteData[off + 2], + ]); + } + + // CPU-side texture mirrors + reusable layout buffers + const textRows = this.maxPlayers * LINES_PER_PLAYER; + this.cpuPlayerData = new Float32Array(8 * this.maxPlayers * 4); + this.cpuStringData = new Uint8Array(MAX_CHARS * textRows); + this.cpuCursorData = new Float32Array(MAX_CHARS * textRows); + this.stringRow = new Uint8Array(MAX_CHARS); + this.cursorRow = new Float32Array(MAX_CHARS); + + // Shared VAO (unit [0,1]² quad) + this.vao = createFullscreenQuad(gl); + + // Data textures + this.glyphMetricsTex = buildGlyphMetricsTex(gl, atlas); + this.cursorTex = buildCursorTex(gl, this.maxPlayers); + this.stringTex = buildStringTex(gl, this.maxPlayers); + this.playerDataTex = buildPlayerDataTex(gl, this.maxPlayers); + + // Sub-programs + this.textProgram = new TextProgram(gl, atlas, { + glyphMetrics: this.glyphMetricsTex, + cursor: this.cursorTex, + strings: this.stringTex, + playerData: this.playerDataTex, + }); + this.iconProgram = new IconProgram( + gl, + atlas, + this.playerDataTex, + this.maxPlayers, + ); + this.statusIconProgram = new StatusIconProgram( + gl, + atlas, + this.playerDataTex, + this.maxPlayers, + ); + this.debugProgram = new DebugProgram( + gl, + atlas, + this.playerDataTex, + this.maxPlayers, + ); + } + + // ------------------------------------------------------------------------- + // Late player registration (bots arrive on tick 1) + // ------------------------------------------------------------------------- + + /** Register players that arrived after construction (palette already updated). */ + addPlayers(players: PlayerStatic[], paletteData: Float32Array): void { + for (const p of players) { + if (this.playerByID.has(p.id)) continue; + this.playerByID.set(p.id, p); + this.smallIDToPlayerID.set(p.smallID, p.id); + const off = p.smallID * 4; + this.playerColors.set(p.id, [ + paletteData[off], + paletteData[off + 1], + paletteData[off + 2], + ]); + } + } + + // ------------------------------------------------------------------------- + // Name updates — called by GPURenderer + // ------------------------------------------------------------------------- + + updateNames( + names: Map, + players: Map, + snap: boolean, + statusData?: Map, + ): void { + const now = performance.now() / 1000; + + // Build alive set and emoji lookup from smallID → playerID + const alivePlayerIDs = this.alivePlayerIDs; + alivePlayerIDs.clear(); + const troopsByPlayerID = this.troopsByPlayerID; + troopsByPlayerID.clear(); + const playerStateByID = this.playerStateByID; + playerStateByID.clear(); + for (const [, ps] of players) { + const pid = this.smallIDToPlayerID.get(ps.smallID); + if (!pid) continue; + if (ps.isAlive) alivePlayerIDs.add(pid); + troopsByPlayerID.set(pid, ps.troops ?? 0); + playerStateByID.set(pid, ps); + } + + // Assign slot indices to players (stable ordering by header index) + let nextSlotIndex = 0; + for (const p of this.playerByID.values()) { + if (!this.slots.has(p.id)) { + const flagCode = p.flag; + this.slots.set(p.id, { + index: nextSlotIndex++, + playerID: p.id, + static: p, + srcX: 0, + srcY: 0, + srcScale: 0, + tgtX: 0, + tgtY: 0, + tgtScale: 0, + startTime: now, + alive: false, + nameLen: 0, + troopLen: 0, + lastTroopStr: "", + flagAtlasIdx: flagCode + ? (this.flagCodeToIndex.get(flagCode) ?? -1) + : -1, + emojiAtlasIdx: -1, + nameHalfWidth: 0, + crown: false, + traitor: false, + disconnected: false, + alliance: false, + allianceReq: false, + target: false, + embargo: false, + nukeActive: false, + nukeTargetsMe: false, + traitorRemainingTicks: 0, + allianceFraction: 0, + }); + } else { + nextSlotIndex = Math.max( + nextSlotIndex, + this.slots.get(p.id)!.index + 1, + ); + } + } + + for (const [playerID, entry] of names) { + const slot = this.slots.get(playerID); + if (!slot) continue; + + const alive = alivePlayerIDs.has(playerID); + + // Skip dead players already marked dead — no work needed + if (!alive && !slot.alive) continue; + + // Newly dead: mark and write once, then skip expensive work + if (!alive && slot.alive) { + slot.alive = false; + this.writePlayerDataRow(slot); + continue; + } + + // Track whether anything changed that requires a GPU write + let dirty = !slot.alive; // first time alive → must write + slot.alive = alive; + + // Write name string (only on first encounter) + if (slot.nameLen === 0) { + const name = slot.static.displayName; + slot.nameLen = Math.min(name.length, MAX_CHARS); + slot.nameHalfWidth = this.uploadStringRow( + slot.index * LINES_PER_PLAYER, + name, + ); + dirty = true; + } + + // Write troop count string (only if changed) + const troops = troopsByPlayerID.get(playerID) ?? 0; + const troopStr = formatTroops(troops); + if (troopStr !== slot.lastTroopStr) { + slot.troopLen = Math.min(troopStr.length, MAX_CHARS); + slot.lastTroopStr = troopStr; + this.uploadStringRow(slot.index * LINES_PER_PLAYER + 1, troopStr); + dirty = true; + } + + // Check if target position changed — only then recompute lerp source + if ( + entry.x !== slot.tgtX || + entry.y !== slot.tgtY || + entry.size !== slot.tgtScale + ) { + if (!snap) { + const elapsed = now - slot.startTime; + const t = Math.min( + 1 - Math.exp(-this.settings.name.lerpSpeed * elapsed), + 1, + ); + slot.srcX = slot.srcX + (slot.tgtX - slot.srcX) * t; + slot.srcY = slot.srcY + (slot.tgtY - slot.srcY) * t; + slot.srcScale = slot.srcScale + (slot.tgtScale - slot.srcScale) * t; + } else { + slot.srcX = entry.x; + slot.srcY = entry.y; + slot.srcScale = entry.size; + } + slot.tgtX = entry.x; + slot.tgtY = entry.y; + slot.tgtScale = entry.size; + slot.startTime = now; + dirty = true; + } + + // Resolve active broadcast emoji for this player + let newEmoji = -1; + const ps = playerStateByID.get(playerID); + if (ps?.outgoingEmojis && ps.outgoingEmojis.length > 0) { + for (const e of ps.outgoingEmojis) { + if (e.recipientID === "AllPlayers") { + const idx = this.emojiCharToIndex.get(e.message); + if (idx !== undefined) { + newEmoji = idx; + break; + } + } + } + } + if (newEmoji !== slot.emojiAtlasIdx) { + slot.emojiAtlasIdx = newEmoji; + dirty = true; + } + + // Resolve status data from per-player map — diff each field + const sd = statusData?.get(slot.static.smallID); + const crown = sd?.crown ?? false; + const traitor = sd?.traitor ?? false; + const disconnected = sd?.disconnected ?? false; + const alliance = sd?.alliance ?? false; + const allianceReq = sd?.allianceReq ?? false; + const target = sd?.target ?? false; + const embargo = sd?.embargo ?? false; + const nukeActive = sd?.nukeActive ?? false; + const nukeTargetsMe = sd?.nukeTargetsMe ?? false; + const traitorRemainingTicks = sd?.traitorRemainingTicks ?? 0; + const allianceFraction = sd?.allianceFraction ?? 0; + + if ( + crown !== slot.crown || + traitor !== slot.traitor || + disconnected !== slot.disconnected || + alliance !== slot.alliance || + allianceReq !== slot.allianceReq || + target !== slot.target || + embargo !== slot.embargo || + nukeActive !== slot.nukeActive || + nukeTargetsMe !== slot.nukeTargetsMe || + traitorRemainingTicks !== slot.traitorRemainingTicks || + allianceFraction !== slot.allianceFraction + ) { + slot.crown = crown; + slot.traitor = traitor; + slot.disconnected = disconnected; + slot.alliance = alliance; + slot.allianceReq = allianceReq; + slot.target = target; + slot.embargo = embargo; + slot.nukeActive = nukeActive; + slot.nukeTargetsMe = nukeTargetsMe; + slot.traitorRemainingTicks = traitorRemainingTicks; + slot.allianceFraction = allianceFraction; + dirty = true; + } + + if (dirty) this.writePlayerDataRow(slot); + } + + // Update alive/dead status for players not in the names map + for (const [pid, slot] of this.slots) { + if (!names.has(pid) && slot.alive) { + slot.alive = false; + this.writePlayerDataRow(slot); + } + } + } + + // ------------------------------------------------------------------------- + // Texture sub-update helpers + // ------------------------------------------------------------------------- + + /** Lay out a string into CPU buffers (flushed to GPU in draw). Returns halfWidth. */ + private uploadStringRow(row: number, text: string): number { + const halfWidth = layoutString( + text, + this.glyph, + this.kernTable, + this.stringRow, + this.cursorRow, + ); + + const off = row * MAX_CHARS; + this.cpuStringData.set(this.stringRow, off); + this.cpuCursorData.set(this.cursorRow, off); + this.stringDataDirty = true; + this.cursorDataDirty = true; + + return halfWidth; + } + + /** Pack player data into the CPU buffer (flushed to GPU in draw). */ + private writePlayerDataRow(slot: PlayerSlot): void { + const d = this.cpuPlayerData; + const off = slot.index * 32; // 8 columns × 4 floats per RGBA texel + + // Column 0: srcX, srcY, srcScale, startTime + d[off + 0] = slot.srcX; + d[off + 1] = slot.srcY; + d[off + 2] = slot.srcScale; + d[off + 3] = slot.startTime; + + // Column 1: tgtX, tgtY, tgtScale, alive + d[off + 4] = slot.tgtX; + d[off + 5] = slot.tgtY; + d[off + 6] = slot.tgtScale; + d[off + 7] = slot.alive ? 1.0 : 0.0; + + // Column 2: player territory color (r, g, b) + alpha + const color = this.playerColors.get(slot.playerID) ?? [0, 0, 0]; + d[off + 8] = color[0]; + d[off + 9] = color[1]; + d[off + 10] = color[2]; + d[off + 11] = 1.0; + + // Column 3: nameLen, troopLen, isHuman, nameHalfWidth + d[off + 12] = slot.nameLen; + d[off + 13] = slot.troopLen; + d[off + 14] = slot.static.playerType === PlayerTypeEnum.Human ? 1.0 : 0.0; + d[off + 15] = slot.nameHalfWidth; + + // Column 4: flagAtlasIdx, emojiAtlasIdx, [free], [free] + d[off + 16] = slot.flagAtlasIdx; + d[off + 17] = slot.emojiAtlasIdx; + d[off + 18] = 0; + d[off + 19] = 0; + + // Column 5: crown, traitor, disconnected, alliance + d[off + 20] = slot.crown ? 1.0 : 0.0; + d[off + 21] = slot.traitor ? 1.0 : 0.0; + d[off + 22] = slot.disconnected ? 1.0 : 0.0; + d[off + 23] = slot.alliance ? 1.0 : 0.0; + + // Column 6: allianceReq, target, embargo, nukeActive + d[off + 24] = slot.allianceReq ? 1.0 : 0.0; + d[off + 25] = slot.target ? 1.0 : 0.0; + d[off + 26] = slot.embargo ? 1.0 : 0.0; + d[off + 27] = slot.nukeActive ? 1.0 : 0.0; + + // Column 7: nukeTargetsMe, traitorRemainingTicks, allianceFraction, [free] + d[off + 28] = slot.nukeTargetsMe ? 1.0 : 0.0; + d[off + 29] = slot.traitorRemainingTicks; + d[off + 30] = slot.allianceFraction; + d[off + 31] = 0; + + this.playerDataDirty = true; + } + + // ------------------------------------------------------------------------- + // Render + // ------------------------------------------------------------------------- + + draw(cameraMatrix: Float32Array, ambient: number): void { + if (!this.textProgram.ready) return; + if (this.slots.size === 0) return; + + const gl = this.gl; + if (this.stringDataDirty) { + gl.bindTexture(gl.TEXTURE_2D, this.stringTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + MAX_CHARS, + this.maxPlayers * LINES_PER_PLAYER, + gl.RED_INTEGER, + gl.UNSIGNED_BYTE, + this.cpuStringData, + ); + this.stringDataDirty = false; + } + if (this.cursorDataDirty) { + gl.bindTexture(gl.TEXTURE_2D, this.cursorTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + MAX_CHARS, + this.maxPlayers * LINES_PER_PLAYER, + gl.RED, + gl.FLOAT, + this.cpuCursorData, + ); + this.cursorDataDirty = false; + } + if (this.playerDataDirty) { + gl.bindTexture(gl.TEXTURE_2D, this.playerDataTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + 8, + this.maxPlayers, + gl.RGBA, + gl.FLOAT, + this.cpuPlayerData, + ); + this.playerDataDirty = false; + } + + this.textProgram.draw( + cameraMatrix, + this.settings, + this.vao, + this.maxPlayers, + ambient, + ); + this.iconProgram.draw(cameraMatrix, this.settings, this.vao); + this.statusIconProgram.draw(cameraMatrix, this.settings, this.vao); + + if (this.settings.passEnabled.nameDebug) { + this.debugProgram.draw(cameraMatrix, this.settings, this.vao); + } + } + + // ------------------------------------------------------------------------- + // Lifecycle + // ------------------------------------------------------------------------- + + dispose(): void { + const gl = this.gl; + this.textProgram.dispose(); + this.iconProgram.dispose(); + this.statusIconProgram.dispose(); + this.debugProgram.dispose(); + gl.deleteTexture(this.glyphMetricsTex); + gl.deleteTexture(this.cursorTex); + gl.deleteTexture(this.stringTex); + gl.deleteTexture(this.playerDataTex); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/name-pass/status-icon-program.ts b/src/client/render/gl/passes/name-pass/status-icon-program.ts new file mode 100644 index 0000000000..6f04ad5b68 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/status-icon-program.ts @@ -0,0 +1,163 @@ +/** + * StatusIconProgram — instanced status icons above player names. + * + * Renders up to 8 status icons per player (crown, traitor, disconnected, + * alliance, alliance request, target, embargo, nuke). Each instance reads + * individual float flags from pd5/pd6 to decide whether to draw. + * + * Owns: shader program, uniform locations, status atlas texture. + * The shared playerDataTex is passed in but not owned/deleted. + */ + +import statusAtlasMeta from "../../assets/status-atlas-meta.json"; +import statusAtlasUrl from "../../assets/status-atlas.png?url"; +import type { RenderSettings } from "../../render-settings"; +import statusFragSrc from "../../shaders/name/status-icon.frag.glsl?raw"; +import statusVertSrc from "../../shaders/name/status-icon.vert.glsl?raw"; +import { createProgram } from "../../utils/gl-utils"; +import type { ParsedAtlas } from "./types"; + +const MAX_STATUS_ICONS = 8; + +export class StatusIconProgram { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private playerDataTex: WebGLTexture; + private maxPlayers: number; + + private statusAtlasTex: WebGLTexture | null = null; + private atlasReady = false; + + // Dynamic uniform locations + private uCamera: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uLerpSpeed: WebGLUniformLocation; + private uCullThreshold: WebGLUniformLocation; + private uNameScaleFactor: WebGLUniformLocation; + private uNameScaleCap: WebGLUniformLocation; + private uStatusRowOffset: WebGLUniformLocation; + + constructor( + gl: WebGL2RenderingContext, + atlas: ParsedAtlas, + playerDataTex: WebGLTexture, + maxPlayers: number, + ) { + this.gl = gl; + this.playerDataTex = playerDataTex; + this.maxPlayers = maxPlayers; + + this.program = createProgram(gl, statusVertSrc, statusFragSrc); + gl.useProgram(this.program); + + // Texture unit bindings + gl.uniform1i(gl.getUniformLocation(this.program, "uPlayerData"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uStatusAtlas"), 1); + + // Static uniforms from atlas metadata + const sm = statusAtlasMeta as any; + gl.uniform1f( + gl.getUniformLocation(this.program, "uFontSize")!, + atlas.fontSize, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uFontBase")!, atlas.base); + gl.uniform1f( + gl.getUniformLocation(this.program, "uStatusCell")!, + sm.cellSize, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uStatusCols")!, sm.cols); + gl.uniform1f( + gl.getUniformLocation(this.program, "uStatusAtlasW")!, + sm.width, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uStatusAtlasH")!, + sm.height, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uStatusPad")!, + sm.pad ?? 0, + ); + + // Dynamic uniform locations + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uLerpSpeed = gl.getUniformLocation(this.program, "uLerpSpeed")!; + this.uCullThreshold = gl.getUniformLocation( + this.program, + "uCullThreshold", + )!; + this.uNameScaleFactor = gl.getUniformLocation( + this.program, + "uNameScaleFactor", + )!; + this.uNameScaleCap = gl.getUniformLocation(this.program, "uNameScaleCap")!; + this.uStatusRowOffset = gl.getUniformLocation( + this.program, + "uStatusRowOffset", + )!; + + this.loadAtlas(); + } + + private loadAtlas(): void { + const gl = this.gl; + const img = new Image(); + img.onload = () => { + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + gl.LINEAR_MIPMAP_LINEAR, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + gl.generateMipmap(gl.TEXTURE_2D); + this.statusAtlasTex = tex; + this.atlasReady = true; + }; + img.src = statusAtlasUrl; + } + + draw( + cameraMatrix: Float32Array, + settings: RenderSettings, + vao: WebGLVertexArrayObject, + ): void { + if (!this.atlasReady) return; + + const gl = this.gl; + const ns = settings.name; + gl.useProgram(this.program); + + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTime, performance.now() / 1000); + gl.uniform1f(this.uLerpSpeed, ns.lerpSpeed); + gl.uniform1f(this.uCullThreshold, ns.cullThreshold); + gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor); + gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap); + gl.uniform1f(this.uStatusRowOffset, ns.statusRowOffset); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.playerDataTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.statusAtlasTex!); + + gl.bindVertexArray(vao); + gl.drawArraysInstanced( + gl.TRIANGLES, + 0, + 6, + this.maxPlayers * MAX_STATUS_ICONS, + ); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + if (this.statusAtlasTex) gl.deleteTexture(this.statusAtlasTex); + } +} diff --git a/src/client/render/gl/passes/name-pass/text-layout.ts b/src/client/render/gl/passes/name-pass/text-layout.ts new file mode 100644 index 0000000000..5806084bcf --- /dev/null +++ b/src/client/render/gl/passes/name-pass/text-layout.ts @@ -0,0 +1,74 @@ +/** + * Pure CPU text shaping — cursor position computation and number formatting. + * No WebGL dependency. + */ + +import type { GlyphTables } from "./atlas-data"; +import { CHAR_RANGE, MAX_CHARS } from "./types"; + +export interface LayoutResult { + charCodes: Uint8Array; // char code per slot (MAX_CHARS, zero-padded) + cursors: Float32Array; // centered cursor X per slot (MAX_CHARS) + halfWidth: number; // visual half-width in font units +} + +/** + * Lay out a string: encode char codes, compute advance-based cursor X + * positions, then center on visual bounds. + * + * Writes into caller-provided buffers to avoid allocation. + */ +export function layoutString( + text: string, + glyph: GlyphTables, + kernTable: Int8Array, + charCodes: Uint8Array, + cursors: Float32Array, +): number { + charCodes.fill(0); + cursors.fill(0); + const len = Math.min(text.length, MAX_CHARS); + + for (let i = 0; i < len; i++) { + charCodes[i] = text.charCodeAt(i); + } + + // Advance-based cursor positions + let cumulative = 0; + let prevCode = 0; + for (let i = 0; i < len; i++) { + const code = charCodes[i]; + cursors[i] = cumulative; + let adv = glyph.advance[code]; + if (i > 0) { + adv += kernTable[prevCode * CHAR_RANGE + code]; + } + cumulative += adv; + prevCode = code; + } + + // Center on visual bounds (not advance bounds) + const firstCode = charCodes[0]; + const lastCode = charCodes[len - 1]; + const visualLeft = cursors[0] + glyph.xOffset[firstCode]; + const visualRight = + cursors[len - 1] + glyph.xOffset[lastCode] + glyph.visW[lastCode]; + const visualCenter = (visualLeft + visualRight) * 0.5; + for (let i = 0; i < len; i++) { + cursors[i] -= visualCenter; + } + + return (visualRight - visualLeft) * 0.5; +} + +/** Format internal troop count for display (internal values are 10x display). */ +export function formatTroops(internalTroops: number): string { + const troops = internalTroops / 10; + if (troops >= 1_000_000) { + return (troops / 1_000_000).toFixed(1) + "M"; + } + if (troops >= 1_000) { + return (troops / 1_000).toFixed(1) + "K"; + } + return troops.toFixed(0); +} diff --git a/src/client/render/gl/passes/name-pass/text-program.ts b/src/client/render/gl/passes/name-pass/text-program.ts new file mode 100644 index 0000000000..8db2cadae8 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/text-program.ts @@ -0,0 +1,197 @@ +/** + * TextProgram — MSDF text rendering (player names + troop counts). + * + * Owns: shader program, uniform locations, MSDF atlas texture (async loaded). + * Shared textures (glyphMetrics, cursor, strings, playerData) are passed in + * and bound at draw time but not owned/deleted by this class. + */ + +import atlasUrl from "../../assets/msdf-atlas.png?url"; +import type { RenderSettings } from "../../render-settings"; +import nameFragSrc from "../../shaders/name/name.frag.glsl?raw"; +import nameVertSrc from "../../shaders/name/name.vert.glsl?raw"; +import { createProgram, shaderSrc } from "../../utils/gl-utils"; +import type { ParsedAtlas } from "./types"; +import { LINES_PER_PLAYER, MAX_CHARS } from "./types"; + +export interface TextProgramTextures { + glyphMetrics: WebGLTexture; + cursor: WebGLTexture; + strings: WebGLTexture; + playerData: WebGLTexture; +} + +export class TextProgram { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private textures: TextProgramTextures; + + // Async-loaded MSDF atlas + private atlasTex: WebGLTexture | null = null; + private atlasReady = false; + + // Uniform locations + private uCamera: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uDistRange: WebGLUniformLocation; + private uLerpSpeed: WebGLUniformLocation; + private uCullThreshold: WebGLUniformLocation; + private uNameScaleFactor: WebGLUniformLocation; + private uNameScaleCap: WebGLUniformLocation; + private uTroopSizeMultiplier: WebGLUniformLocation; + private uOutlineWidth: WebGLUniformLocation; + private uNightAmbient: WebGLUniformLocation; + private uOutlineColor: WebGLUniformLocation; + private uOutlineUsePlayerColor: WebGLUniformLocation; + private uFillUsePlayerColor: WebGLUniformLocation; + + private distanceRange: number; + + constructor( + gl: WebGL2RenderingContext, + atlas: ParsedAtlas, + textures: TextProgramTextures, + ) { + this.gl = gl; + this.textures = textures; + this.distanceRange = atlas.distanceRange; + + this.program = createProgram( + gl, + shaderSrc(nameVertSrc, { MAX_CHARS, LINES_PER_PLAYER }), + nameFragSrc, + ); + + // Texture unit bindings + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uAtlas"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uGlyphMetrics"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uCursorX"), 2); + gl.uniform1i(gl.getUniformLocation(this.program, "uStrings"), 3); + gl.uniform1i(gl.getUniformLocation(this.program, "uPlayerData"), 4); + + // Static uniforms + gl.uniform1f( + gl.getUniformLocation(this.program, "uFontSize")!, + atlas.fontSize, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uAtlasScaleW")!, + atlas.scaleW, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uAtlasScaleH")!, + atlas.scaleH, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uBase")!, atlas.base); + + // Dynamic uniform locations + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uDistRange = gl.getUniformLocation(this.program, "uDistRange")!; + this.uLerpSpeed = gl.getUniformLocation(this.program, "uLerpSpeed")!; + this.uCullThreshold = gl.getUniformLocation( + this.program, + "uCullThreshold", + )!; + this.uNameScaleFactor = gl.getUniformLocation( + this.program, + "uNameScaleFactor", + )!; + this.uNameScaleCap = gl.getUniformLocation(this.program, "uNameScaleCap")!; + this.uTroopSizeMultiplier = gl.getUniformLocation( + this.program, + "uTroopSizeMultiplier", + )!; + this.uOutlineWidth = gl.getUniformLocation(this.program, "uOutlineWidth")!; + this.uNightAmbient = gl.getUniformLocation(this.program, "uNightAmbient")!; + this.uOutlineColor = gl.getUniformLocation(this.program, "uOutlineColor")!; + this.uOutlineUsePlayerColor = gl.getUniformLocation( + this.program, + "uOutlineUsePlayerColor", + )!; + this.uFillUsePlayerColor = gl.getUniformLocation( + this.program, + "uFillUsePlayerColor", + )!; + + this.loadAtlas(); + } + + get ready(): boolean { + return this.atlasReady; + } + + private loadAtlas(): void { + const gl = this.gl; + const img = new Image(); + img.onload = () => { + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + this.atlasTex = tex; + this.atlasReady = true; + }; + img.src = atlasUrl; + } + + draw( + cameraMatrix: Float32Array, + settings: RenderSettings, + vao: WebGLVertexArrayObject, + maxPlayers: number, + ambient: number, + ): void { + if (!this.atlasReady) return; + + const gl = this.gl; + const ns = settings.name; + gl.useProgram(this.program); + + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTime, performance.now() / 1000); + gl.uniform1f(this.uDistRange, this.distanceRange); + gl.uniform1f(this.uLerpSpeed, ns.lerpSpeed); + gl.uniform1f(this.uCullThreshold, ns.cullThreshold); + gl.uniform1f(this.uNameScaleFactor, ns.nameScaleFactor); + gl.uniform1f(this.uNameScaleCap, ns.nameScaleCap); + gl.uniform1f(this.uTroopSizeMultiplier, ns.troopSizeMultiplier); + gl.uniform1f(this.uOutlineWidth, ns.outlineWidth); + gl.uniform1f(this.uNightAmbient, ambient); + gl.uniform3f(this.uOutlineColor, ns.outlineR, ns.outlineG, ns.outlineB); + gl.uniform1f( + this.uOutlineUsePlayerColor, + ns.outlineUsePlayerColor ? 1.0 : 0.0, + ); + gl.uniform1f(this.uFillUsePlayerColor, ns.fillUsePlayerColor ? 1.0 : 0.0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex!); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.textures.glyphMetrics); + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.textures.cursor); + gl.activeTexture(gl.TEXTURE3); + gl.bindTexture(gl.TEXTURE_2D, this.textures.strings); + gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this.textures.playerData); + + gl.bindVertexArray(vao); + gl.drawArraysInstanced( + gl.TRIANGLES, + 0, + 6, + maxPlayers * LINES_PER_PLAYER * MAX_CHARS, + ); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + if (this.atlasTex) gl.deleteTexture(this.atlasTex); + } +} diff --git a/src/client/render/gl/passes/name-pass/types.ts b/src/client/render/gl/passes/name-pass/types.ts new file mode 100644 index 0000000000..85e3d9c8b7 --- /dev/null +++ b/src/client/render/gl/passes/name-pass/types.ts @@ -0,0 +1,88 @@ +/** + * Shared types and constants for the NamePass subsystem. + */ + +// --------------------------------------------------------------------------- +// BMFont JSON types +// --------------------------------------------------------------------------- + +export interface BMChar { + id: number; + char: string; + width: number; + height: number; + xoffset: number; + yoffset: number; + xadvance: number; + x: number; + y: number; + page: number; +} + +export interface BMKerning { + first: number; + second: number; + amount: number; +} + +export interface ParsedAtlas { + fontSize: number; + base: number; + scaleW: number; + scaleH: number; + distanceRange: number; + chars: BMChar[]; + kernings: BMKerning[]; +} + +// --------------------------------------------------------------------------- +// Per-player CPU-side state +// --------------------------------------------------------------------------- + +export interface PlayerSlot { + index: number; + playerID: string; + static: import("../../../types").PlayerStatic; + + srcX: number; + srcY: number; + srcScale: number; + tgtX: number; + tgtY: number; + tgtScale: number; + startTime: number; + + alive: boolean; + nameLen: number; + troopLen: number; + lastTroopStr: string; + flagAtlasIdx: number; + emojiAtlasIdx: number; + nameHalfWidth: number; + + // Status flags (individual booleans, written as 1.0/0.0 to GPU) + crown: boolean; + traitor: boolean; + disconnected: boolean; + alliance: boolean; + allianceReq: boolean; + target: boolean; + embargo: boolean; + nukeActive: boolean; + nukeTargetsMe: boolean; + traitorRemainingTicks: number; + allianceFraction: number; +} + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** Max char ID in the atlas (Latin Extended-A goes to 383). */ +export const CHAR_RANGE = 384; + +/** Max characters per text line (name or troop count). */ +export const MAX_CHARS = 32; + +/** Lines per player: 0 = name, 1 = troop count. */ +export const LINES_PER_PLAYER = 2; diff --git a/src/client/render/gl/passes/night-composite-pass.ts b/src/client/render/gl/passes/night-composite-pass.ts new file mode 100644 index 0000000000..b87146bd4d --- /dev/null +++ b/src/client/render/gl/passes/night-composite-pass.ts @@ -0,0 +1,114 @@ +/** + * NightCompositePass — scene capture + day/night composite. + * + * Owns the scene capture FBO: terrain + territory render into it when + * day/night is enabled. Composites the captured scene with a blurred + * lightmap: output = scene * min(ambient + lightmap, 1.2). + * + * At full daytime (ambient ≈ 1.0) the composite is a visual identity — + * multiplication by ~1.0 — so the pass runs continuously with no threshold. + */ + +import type { RenderSettings } from "../render-settings"; +import { createFullscreenQuad, createProgram } from "../utils/gl-utils"; + +import compositeFragSrc from "../shaders/day-night/composite.frag.glsl?raw"; +import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw"; + +function smoothstep(edge0: number, edge1: number, x: number): number { + const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0))); + return t * t * (3 - 2 * t); +} + +export class NightCompositePass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + + // Composite program + private compositeProg: WebGLProgram; + private uCompositeAmbient: WebGLUniformLocation; + private quadVao: WebGLVertexArrayObject; + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + + // --- Composite program --- + this.compositeProg = createProgram(gl, fullscreenVertSrc, compositeFragSrc); + this.uCompositeAmbient = gl.getUniformLocation( + this.compositeProg, + "uAmbient", + )!; + gl.useProgram(this.compositeProg); + gl.uniform1i(gl.getUniformLocation(this.compositeProg, "uSceneTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.compositeProg, "uLightTex"), 1); + + // --- Fullscreen quad --- + this.quadVao = createFullscreenQuad(gl); + } + + // ------------------------------------------------------------------------- + // Ambient + // ------------------------------------------------------------------------- + + getAmbient(tick: number): number { + const dn = this.settings.dayNight; + + if (dn.mode === "light") return dn.dayAmbient; + if (dn.mode === "dark") return dn.nightAmbient; + + // Normalize phase to [0, 1), 0 = noon + const phase = (((tick / dn.cycleTicks + dn.startPhase) % 1) + 1) % 1; + + // Clamp holds so they never exceed the full cycle + const noonHold = Math.min(dn.noonHold, 1); + const nightHold = Math.min(dn.nightHold, Math.max(0, 1 - noonHold)); + const halfTransition = (1 - noonHold - nightHold) / 2; + + // Region boundaries (all in [0, 1)) + const duskStart = noonHold / 2; + const duskEnd = duskStart + halfTransition; // = 0.5 - nightHold/2 + const nightEnd = duskEnd + nightHold; // = 0.5 + nightHold/2 + const dawnEnd = nightEnd + halfTransition; // = 1 - noonHold/2 + + let t: number; + if (phase < duskStart || phase >= dawnEnd) { + t = 1; // noon hold + } else if (phase < duskEnd) { + t = smoothstep(duskEnd, duskStart, phase); // day → night + } else if (phase < nightEnd) { + t = 0; // midnight hold + } else { + t = smoothstep(nightEnd, dawnEnd, phase); // night → day + } + + return dn.nightAmbient + (dn.dayAmbient - dn.nightAmbient) * t; + } + + // ------------------------------------------------------------------------- + // Composite: scene * (ambient + lightmap) → screen + // ------------------------------------------------------------------------- + + /** Pure combiner — receives captured scene + lightmap textures, outputs to screen. */ + draw(tick: number, sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void { + const gl = this.gl; + gl.disable(gl.BLEND); + + gl.useProgram(this.compositeProg); + gl.uniform1f(this.uCompositeAmbient, this.getAmbient(tick)); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, sceneTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, lightmapTex); + + gl.bindVertexArray(this.quadVao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.compositeProg); + gl.deleteVertexArray(this.quadVao); + } +} diff --git a/src/client/render/gl/passes/nuke-telegraph-pass.ts b/src/client/render/gl/passes/nuke-telegraph-pass.ts new file mode 100644 index 0000000000..17ee6a4198 --- /dev/null +++ b/src/client/render/gl/passes/nuke-telegraph-pass.ts @@ -0,0 +1,152 @@ +/** + * NukeTelegraphPass — renders animated blast-radius circles at the target + * location of each in-flight nuke. + * + * Instanced quads with two concentric circle SDFs (inner filled, outer + * dashed ring). Similar to SAMRadiusPass but with different aesthetics. + */ + +import type { NukeTelegraphData } from "../../types"; +import { DynamicInstanceBuffer } from "../dynamic-buffer"; +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; + +import fragSrc from "../shaders/nuke-telegraph/nuke-telegraph.frag.glsl?raw"; +import vertSrc from "../shaders/nuke-telegraph/nuke-telegraph.vert.glsl?raw"; + +// Per-instance: x, y, innerRadius, outerRadius +const FLOATS_PER_INSTANCE = 4; + +export class NukeTelegraphPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + + private uCamera: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uTelegraphStyle: WebGLUniformLocation; + private uTelegraphAlpha: WebGLUniformLocation; + private uTelegraphColor: WebGLUniformLocation; + + private instanceCount = 0; + private startTime = performance.now(); + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + this.program = createProgram(gl, vertSrc, fragSrc); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uTelegraphStyle = gl.getUniformLocation( + this.program, + "uTelegraphStyle", + )!; + this.uTelegraphAlpha = gl.getUniformLocation( + this.program, + "uTelegraphAlpha", + )!; + this.uTelegraphColor = gl.getUniformLocation( + this.program, + "uTelegraphColor", + )!; + + // VAO + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + // Attribute 0: unit quad [0,1] + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Attribute 1: per-instance vec4 (x, y, innerR, outerR) + const glBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer( + gl, + glBuf, + 16, + FLOATS_PER_INSTANCE, + ); + gl.bindBuffer(gl.ARRAY_BUFFER, glBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0); + gl.vertexAttribDivisor(1, 1); + + gl.bindVertexArray(null); + } + + update(data: NukeTelegraphData[]): void { + const count = data.length; + this.instanceBuf.ensureCapacity(count); + + const buf = this.instanceBuf.float32; + for (let i = 0; i < count; i++) { + const d = data[i]; + const off = i * FLOATS_PER_INSTANCE; + buf[off + 0] = d.x; + buf[off + 1] = d.y; + buf[off + 2] = d.innerRadius; + buf[off + 3] = d.outerRadius; + } + + this.instanceCount = count; + + if (count > 0) { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + count * FLOATS_PER_INSTANCE, + ); + } + } + + draw(cameraMatrix: Float32Array): void { + if (this.instanceCount === 0) return; + + const gl = this.gl; + const s = this.settings.nukeTelegraph; + const time = (performance.now() - this.startTime) / 1000; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTime, time); + gl.uniform4f( + this.uTelegraphStyle, + s.strokeWidth, + s.dashLen, + s.gapLen, + s.rotationSpeed, + ); + gl.uniform4f( + this.uTelegraphAlpha, + s.baseAlpha, + s.pulseAmplitude, + s.pulseSpeed, + s.fillAlphaOffset, + ); + gl.uniform3f(this.uTelegraphColor, s.colorR, s.colorG, s.colorB); + + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/nuke-trajectory-pass.ts b/src/client/render/gl/passes/nuke-trajectory-pass.ts new file mode 100644 index 0000000000..cb661bf7e5 --- /dev/null +++ b/src/client/render/gl/passes/nuke-trajectory-pass.ts @@ -0,0 +1,328 @@ +/** + * NukeTrajectoryPass — renders the nuke trajectory preview arc during + * build mode (Atom Bomb / Hydrogen Bomb ghost active). + * + * Renders as a triangle strip with screen-space line width. The cubic + * Bezier is evaluated on the GPU from 4 control-point uniforms; cumulative + * arc distances are pre-computed on the CPU for accurate pixel-space dashing. + * + * Zone boundary circles and SAM intercept X markers are drawn with a + * separate marker program. + */ + +import type { NukeTrajectoryData } from "../../types"; +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; + +import markerFragSrc from "../shaders/nuke-trajectory/nuke-trajectory-marker.frag.glsl?raw"; +import markerVertSrc from "../shaders/nuke-trajectory/nuke-trajectory-marker.vert.glsl?raw"; +import fragSrc from "../shaders/nuke-trajectory/nuke-trajectory.frag.glsl?raw"; +import vertSrc from "../shaders/nuke-trajectory/nuke-trajectory.vert.glsl?raw"; + +const NUM_SEGMENTS = 128; +const VERTS_PER_PAIR = 2; +const FLOATS_PER_VERT = 3; // (t, side, cumDist) + +export class NukeTrajectoryPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + + // Line program + private lineProgram: WebGLProgram; + private lineVAO: WebGLVertexArrayObject; + private lineBuf: WebGLBuffer; + private lineVertexCount: number; + private lineVertices: Float32Array; + private uLineCamera: WebGLUniformLocation; + private uLineP0: WebGLUniformLocation; + private uLineP1: WebGLUniformLocation; + private uLineP2: WebGLUniformLocation; + private uLineP3: WebGLUniformLocation; + private uLinePixelSize: WebGLUniformLocation; + private uLineTUntargetableStart: WebGLUniformLocation; + private uLineTUntargetableEnd: WebGLUniformLocation; + private uLineTSamIntercept: WebGLUniformLocation; + private uLineQuadHalfPx: WebGLUniformLocation; + private uLineLineHalfPx: WebGLUniformLocation; + private uLineOutlineHalfPx: WebGLUniformLocation; + private uLineDashPattern: WebGLUniformLocation; + private uLineLineColor: WebGLUniformLocation; + private uLineInterceptColor: WebGLUniformLocation; + private uLineOutlineColor: WebGLUniformLocation; + private uLineInterceptOutlineColor: WebGLUniformLocation; + + // Marker program + private markerProgram: WebGLProgram; + private markerVAO: WebGLVertexArrayObject; + private uMarkerCamera: WebGLUniformLocation; + private uMarkerP0: WebGLUniformLocation; + private uMarkerP1: WebGLUniformLocation; + private uMarkerP2: WebGLUniformLocation; + private uMarkerP3: WebGLUniformLocation; + private uMarkerPixelSize: WebGLUniformLocation; + private uMarker: WebGLUniformLocation; + private uMarkerRadii: WebGLUniformLocation; + + private visible = false; + private data: NukeTrajectoryData | null = null; + + constructor(gl: WebGL2RenderingContext, settings: RenderSettings) { + this.gl = gl; + this.settings = settings; + + // --- Line program --- + this.lineProgram = createProgram(gl, vertSrc, fragSrc); + this.uLineCamera = gl.getUniformLocation(this.lineProgram, "uCamera")!; + this.uLineP0 = gl.getUniformLocation(this.lineProgram, "uP0")!; + this.uLineP1 = gl.getUniformLocation(this.lineProgram, "uP1")!; + this.uLineP2 = gl.getUniformLocation(this.lineProgram, "uP2")!; + this.uLineP3 = gl.getUniformLocation(this.lineProgram, "uP3")!; + this.uLinePixelSize = gl.getUniformLocation( + this.lineProgram, + "uPixelSize", + )!; + this.uLineTUntargetableStart = gl.getUniformLocation( + this.lineProgram, + "uTUntargetableStart", + )!; + this.uLineTUntargetableEnd = gl.getUniformLocation( + this.lineProgram, + "uTUntargetableEnd", + )!; + this.uLineTSamIntercept = gl.getUniformLocation( + this.lineProgram, + "uTSamIntercept", + )!; + this.uLineQuadHalfPx = gl.getUniformLocation( + this.lineProgram, + "uQuadHalfPx", + )!; + this.uLineLineHalfPx = gl.getUniformLocation( + this.lineProgram, + "uLineHalfPx", + )!; + this.uLineOutlineHalfPx = gl.getUniformLocation( + this.lineProgram, + "uOutlineHalfPx", + )!; + this.uLineDashPattern = gl.getUniformLocation( + this.lineProgram, + "uDashPattern", + )!; + this.uLineLineColor = gl.getUniformLocation( + this.lineProgram, + "uLineColor", + )!; + this.uLineInterceptColor = gl.getUniformLocation( + this.lineProgram, + "uInterceptColor", + )!; + this.uLineOutlineColor = gl.getUniformLocation( + this.lineProgram, + "uOutlineColor", + )!; + this.uLineInterceptOutlineColor = gl.getUniformLocation( + this.lineProgram, + "uInterceptOutlineColor", + )!; + + // Triangle strip: (N+1) pairs of left/right vertices + const N = NUM_SEGMENTS; + this.lineVertexCount = (N + 1) * VERTS_PER_PAIR; + this.lineVertices = new Float32Array( + this.lineVertexCount * FLOATS_PER_VERT, + ); + + this.lineVAO = gl.createVertexArray()!; + gl.bindVertexArray(this.lineVAO); + this.lineBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, this.lineBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + this.lineVertices.byteLength, + gl.DYNAMIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + + // --- Marker program --- + this.markerProgram = createProgram(gl, markerVertSrc, markerFragSrc); + this.uMarkerCamera = gl.getUniformLocation(this.markerProgram, "uCamera")!; + this.uMarkerP0 = gl.getUniformLocation(this.markerProgram, "uP0")!; + this.uMarkerP1 = gl.getUniformLocation(this.markerProgram, "uP1")!; + this.uMarkerP2 = gl.getUniformLocation(this.markerProgram, "uP2")!; + this.uMarkerP3 = gl.getUniformLocation(this.markerProgram, "uP3")!; + this.uMarkerPixelSize = gl.getUniformLocation( + this.markerProgram, + "uPixelSize", + )!; + this.uMarker = gl.getUniformLocation(this.markerProgram, "uMarker")!; + this.uMarkerRadii = gl.getUniformLocation( + this.markerProgram, + "uMarkerRadii", + )!; + + this.markerVAO = gl.createVertexArray()!; + gl.bindVertexArray(this.markerVAO); + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([-1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + } + + update(data: NukeTrajectoryData | null): void { + this.data = data; + this.visible = data !== null; + if (data) this.rebuildVertices(data); + } + + /** Recompute triangle strip vertices with cumulative arc distances. */ + private rebuildVertices(d: NukeTrajectoryData): void { + const N = NUM_SEGMENTS; + const buf = this.lineVertices; + let cumDist = 0; + let prevX = d.p0x; + let prevY = d.p0y; + + for (let i = 0; i <= N; i++) { + const t = i / N; + const T = 1 - t; + const TT = T * T; + const tt = t * t; + const x = + TT * T * d.p0x + + 3 * TT * t * d.p1x + + 3 * T * tt * d.p2x + + tt * t * d.p3x; + const y = + TT * T * d.p0y + + 3 * TT * t * d.p1y + + 3 * T * tt * d.p2y + + tt * t * d.p3y; + + if (i > 0) { + const dx = x - prevX; + const dy = y - prevY; + cumDist += Math.sqrt(dx * dx + dy * dy); + } + prevX = x; + prevY = y; + + const idx = i * VERTS_PER_PAIR * FLOATS_PER_VERT; + buf[idx + 0] = t; + buf[idx + 1] = -1; + buf[idx + 2] = cumDist; + buf[idx + 3] = t; + buf[idx + 4] = 1; + buf[idx + 5] = cumDist; + } + + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.lineBuf); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, buf); + } + + draw(cameraMatrix: Float32Array): void { + if (!this.visible || !this.data) return; + + const gl = this.gl; + const d = this.data; + const s = this.settings.nukeTrajectory; + const pixelSize = 2.0 / (cameraMatrix[0] * gl.drawingBufferWidth); + + // Derived pixel dimensions + const lineHalfPx = s.lineWidth / 2; + const outlineHalfPx = (s.lineWidth + s.outlineWidth) / 2; + const quadHalfPx = outlineHalfPx + 1.0; // AA padding + + // --- Draw trajectory line --- + gl.useProgram(this.lineProgram); + gl.uniformMatrix3fv(this.uLineCamera, false, cameraMatrix); + gl.uniform2f(this.uLineP0, d.p0x, d.p0y); + gl.uniform2f(this.uLineP1, d.p1x, d.p1y); + gl.uniform2f(this.uLineP2, d.p2x, d.p2y); + gl.uniform2f(this.uLineP3, d.p3x, d.p3y); + gl.uniform1f(this.uLinePixelSize, pixelSize); + gl.uniform1f(this.uLineTUntargetableStart, d.tUntargetableStart); + gl.uniform1f(this.uLineTUntargetableEnd, d.tUntargetableEnd); + gl.uniform1f(this.uLineTSamIntercept, d.tSamIntercept); + gl.uniform1f(this.uLineQuadHalfPx, quadHalfPx); + gl.uniform1f(this.uLineLineHalfPx, lineHalfPx); + gl.uniform1f(this.uLineOutlineHalfPx, outlineHalfPx); + gl.uniform4f( + this.uLineDashPattern, + s.dashTargetable, + s.gapTargetable, + s.dashUntargetable, + s.gapUntargetable, + ); + gl.uniform3f(this.uLineLineColor, s.lineR, s.lineG, s.lineB); + gl.uniform3f( + this.uLineInterceptColor, + s.interceptR, + s.interceptG, + s.interceptB, + ); + gl.uniform3f(this.uLineOutlineColor, s.outlineR, s.outlineG, s.outlineB); + gl.uniform3f( + this.uLineInterceptOutlineColor, + s.interceptOutlineR, + s.interceptOutlineG, + s.interceptOutlineB, + ); + + gl.bindVertexArray(this.lineVAO); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.lineVertexCount); + + // --- Draw markers --- + this.drawMarkers(cameraMatrix, d, pixelSize); + } + + private drawMarkers( + cameraMatrix: Float32Array, + d: NukeTrajectoryData, + pixelSize: number, + ): void { + const markers: [number, number][] = []; + if (d.tUntargetableStart >= 0) { + markers.push([d.tUntargetableStart, 0]); + markers.push([d.tUntargetableEnd, 0]); + } + if (d.tSamIntercept < 1.0) { + markers.push([d.tSamIntercept, 1]); + } + if (markers.length === 0) return; + + const gl = this.gl; + const s = this.settings.nukeTrajectory; + gl.useProgram(this.markerProgram); + gl.uniformMatrix3fv(this.uMarkerCamera, false, cameraMatrix); + gl.uniform2f(this.uMarkerP0, d.p0x, d.p0y); + gl.uniform2f(this.uMarkerP1, d.p1x, d.p1y); + gl.uniform2f(this.uMarkerP2, d.p2x, d.p2y); + gl.uniform2f(this.uMarkerP3, d.p3x, d.p3y); + gl.uniform1f(this.uMarkerPixelSize, pixelSize); + gl.uniform2f(this.uMarkerRadii, s.markerCircleRadius, s.markerXRadius); + + gl.bindVertexArray(this.markerVAO); + for (const [t, type] of markers) { + gl.uniform4f(this.uMarker, t, type, 0, 0); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.lineProgram); + gl.deleteProgram(this.markerProgram); + gl.deleteVertexArray(this.lineVAO); + gl.deleteVertexArray(this.markerVAO); + } +} diff --git a/src/client/render/gl/passes/point-light-pass.ts b/src/client/render/gl/passes/point-light-pass.ts new file mode 100644 index 0000000000..bbe535fac5 --- /dev/null +++ b/src/client/render/gl/passes/point-light-pass.ts @@ -0,0 +1,237 @@ +/** + * PointLightPass — instanced radial-falloff quads for unit/structure lights. + * + * Single VBO/VAO: units and structures packed together, uploaded once per tick. + * draw() is pure GPU: uniforms + one drawArraysInstanced call. + */ + +import type { RendererConfig, UnitState } from "../../types"; +import { + UT_ATOM_BOMB, + UT_CITY, + UT_DEFENSE_POST, + UT_FACTORY, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_MIRV_WARHEAD, + UT_MISSILE_SILO, + UT_PORT, + UT_SAM_LAUNCHER, + UT_TRADE_SHIP, + UT_TRAIN, + UT_TRANSPORT, + UT_WARSHIP, +} from "../../types"; +import type { RenderSettings } from "../render-settings"; +import { createProgram, shaderSrc } from "../utils/gl-utils"; + +import lightFragSrc from "../shaders/day-night/light.frag.glsl?raw"; +import lightVertSrc from "../shaders/day-night/light.vert.glsl?raw"; + +// --------------------------------------------------------------------------- +// Light source configuration +// --------------------------------------------------------------------------- + +interface LightConfig { + r: number; + g: number; + b: number; + radius: number; + intensity: number; +} + +const LIGHT_CONFIGS: Record = { + [UT_CITY]: { r: 1.0, g: 0.85, b: 0.5, radius: 18, intensity: 1.2 }, + [UT_PORT]: { r: 1.0, g: 0.75, b: 0.4, radius: 12, intensity: 1.0 }, + [UT_FACTORY]: { r: 1.0, g: 0.6, b: 0.3, radius: 12, intensity: 1.0 }, + [UT_DEFENSE_POST]: { r: 0.8, g: 0.85, b: 1.0, radius: 10, intensity: 0.9 }, + [UT_SAM_LAUNCHER]: { r: 0.8, g: 0.85, b: 1.0, radius: 10, intensity: 0.9 }, + [UT_MISSILE_SILO]: { r: 1.0, g: 0.4, b: 0.2, radius: 10, intensity: 0.9 }, + [UT_TRANSPORT]: { r: 0.9, g: 0.8, b: 0.6, radius: 6, intensity: 2.7 }, + [UT_TRADE_SHIP]: { r: 0.9, g: 0.8, b: 0.6, radius: 6, intensity: 2.7 }, + [UT_WARSHIP]: { r: 0.9, g: 0.85, b: 0.7, radius: 10, intensity: 2.8 }, + [UT_ATOM_BOMB]: { r: 1.0, g: 0.9, b: 0.7, radius: 16, intensity: 1.1 }, + [UT_HYDROGEN_BOMB]: { r: 1.0, g: 0.95, b: 0.6, radius: 22, intensity: 1.3 }, + [UT_MIRV]: { r: 1.0, g: 0.9, b: 0.7, radius: 18, intensity: 1.2 }, + [UT_MIRV_WARHEAD]: { r: 1.0, g: 0.6, b: 0.3, radius: 12, intensity: 1.0 }, + [UT_TRAIN]: { r: 1.0, g: 0.85, b: 0.5, radius: 8, intensity: 2.0 }, +}; + +const FLOATS_PER_LIGHT = 6; +const BYTES_PER_LIGHT = FLOATS_PER_LIGHT * 4; +const MAX_LIGHT_TYPES = 64; +const MAX_LIGHTS = 12288; // units + structures combined + +export class PointLightPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + + // Program + uniforms + private lightProg: WebGLProgram; + private uLightCam: WebGLUniformLocation; + private uRadiusMultiplier: WebGLUniformLocation; + private uRadiusArr: WebGLUniformLocation; + private uIntensityArr: WebGLUniformLocation; + private uFalloffPower: WebGLUniformLocation; + + // Single instance buffer — units + structures packed together + private lightVao: WebGLVertexArrayObject; + private lightBuf: WebGLBuffer; + private lightData: Float32Array; + private lightCount = 0; + + // Type config + private typeToIdx = new Map(); + private typeConfigs: (LightConfig | undefined)[]; + private typeNames: string[]; + private radiusArr = new Float32Array(MAX_LIGHT_TYPES); + private intensityArr = new Float32Array(MAX_LIGHT_TYPES); + private paletteData: Float32Array; + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + paletteData: Float32Array, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.paletteData = paletteData; + this.mapW = header.mapWidth; + + // Build type → light config mapping + this.typeNames = header.unitTypes; + this.typeConfigs = new Array(header.unitTypes.length); + for (let i = 0; i < header.unitTypes.length; i++) { + this.typeConfigs[i] = LIGHT_CONFIGS[header.unitTypes[i]]; + this.typeToIdx.set(header.unitTypes[i], i); + } + + // Light program + this.lightProg = createProgram( + gl, + shaderSrc(lightVertSrc, { MAX_LIGHT_TYPES }), + lightFragSrc, + ); + this.uLightCam = gl.getUniformLocation(this.lightProg, "uCamera")!; + this.uRadiusMultiplier = gl.getUniformLocation( + this.lightProg, + "uRadiusMultiplier", + )!; + this.uRadiusArr = gl.getUniformLocation(this.lightProg, "uRadius")!; + this.uIntensityArr = gl.getUniformLocation(this.lightProg, "uIntensity")!; + this.uFalloffPower = gl.getUniformLocation( + this.lightProg, + "uFalloffPower", + )!; + + // Instance buffer + VAO + this.lightData = new Float32Array(MAX_LIGHTS * FLOATS_PER_LIGHT); + this.lightBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, this.lightBuf); + gl.bufferData(gl.ARRAY_BUFFER, this.lightData.byteLength, gl.DYNAMIC_DRAW); + + this.lightVao = gl.createVertexArray()!; + gl.bindVertexArray(this.lightVao); + + // Attribute 0: quad corner [0,1] + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Attribute 1: per-instance vec3 (x, y, typeIdx) + gl.bindBuffer(gl.ARRAY_BUFFER, this.lightBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 3, gl.FLOAT, false, BYTES_PER_LIGHT, 0); + gl.vertexAttribDivisor(1, 1); + + // Attribute 2: per-instance vec3 (r, g, b) + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 3, gl.FLOAT, false, BYTES_PER_LIGHT, 12); + gl.vertexAttribDivisor(2, 1); + + gl.bindVertexArray(null); + } + + /** Pack all light-emitting entities into the instance buffer and upload. Called every tick. */ + updateLights(units: Map): void { + let count = 0; + + for (const unit of units.values()) { + if (!unit.isActive) continue; + const typeIdx = this.typeToIdx.get(unit.unitType); + if (typeIdx === undefined) continue; + const cfg = this.typeConfigs[typeIdx]; + if (!cfg) continue; + if (count >= MAX_LIGHTS) break; + + const x = unit.pos % this.mapW; + const y = (unit.pos - x) / this.mapW; + const off = count * FLOATS_PER_LIGHT; + const pOff = unit.ownerID * 4; + this.lightData[off + 0] = x; + this.lightData[off + 1] = y; + this.lightData[off + 2] = typeIdx; + this.lightData[off + 3] = this.paletteData[pOff]; + this.lightData[off + 4] = this.paletteData[pOff + 1]; + this.lightData[off + 5] = this.paletteData[pOff + 2]; + count++; + } + + this.lightCount = count; + if (count > 0) { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.lightBuf); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.lightData, + 0, + count * FLOATS_PER_LIGHT, + ); + } + } + + /** + * Render instanced point lights into the currently bound FBO. + * Caller must set up additive blending and viewport. + */ + draw(cameraMatrix: Float32Array): void { + if (this.lightCount === 0) return; + + const gl = this.gl; + const dn = this.settings.dayNight; + + gl.useProgram(this.lightProg); + gl.uniformMatrix3fv(this.uLightCam, false, cameraMatrix); + gl.uniform1f(this.uRadiusMultiplier, dn.lightRadiusMultiplier); + gl.uniform1f(this.uFalloffPower, dn.falloffPower); + + for (let i = 0; i < this.typeNames.length; i++) { + const cfg = this.typeConfigs[i]; + if (!cfg) continue; + const ov = this.settings.lightConfigs[this.typeNames[i]]; + this.radiusArr[i] = ov?.radius ?? cfg.radius; + this.intensityArr[i] = ov?.intensity ?? cfg.intensity; + } + gl.uniform1fv(this.uRadiusArr, this.radiusArr); + gl.uniform1fv(this.uIntensityArr, this.intensityArr); + + gl.bindVertexArray(this.lightVao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.lightCount); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.lightProg); + gl.deleteVertexArray(this.lightVao); + gl.deleteBuffer(this.lightBuf); + } +} diff --git a/src/client/render/gl/passes/radial-menu-pass.ts b/src/client/render/gl/passes/radial-menu-pass.ts new file mode 100644 index 0000000000..d195897b96 --- /dev/null +++ b/src/client/render/gl/passes/radial-menu-pass.ts @@ -0,0 +1,574 @@ +/** + * RadialMenuPass — renders a radial (pie-wheel) context menu as screen-space + * arc segments with emoji icons. + * + * Supports one level of submenus: when a submenu is open, the parent items + * shrink into a smaller inner ring, a back button appears in the center, and + * the submenu items take the outer ring. + * + * Rendering elements (reused for each ring via drawRing): + * 1. Arcs: single quad with SDF annulus + angular segment masking + borders + * 2. Center button: filled circle drawn by the innermost ring + * 3. Icons: instanced quads sampling the emoji atlas + */ + +import type { RadialMenuItem } from "../events"; +import { createProgram } from "../utils/gl-utils"; + +import arcFragSrc from "../shaders/radial-menu/arcs.frag.glsl?raw"; +import arcVertSrc from "../shaders/radial-menu/arcs.vert.glsl?raw"; +import iconFragSrc from "../shaders/radial-menu/icon.frag.glsl?raw"; +import iconVertSrc from "../shaders/radial-menu/icon.vert.glsl?raw"; + +import emojiAtlasMeta from "../assets/emoji-atlas-meta.json"; +import emojiAtlasUrl from "../assets/emoji-atlas.png?url"; + +// --------------------------------------------------------------------------- +// Ring layout configs (CSS pixels) +// --------------------------------------------------------------------------- + +interface RingConfig { + outerR: number; + innerR: number; + /** Icon half-size; if a function, receives the segment count. */ + iconHalf: number | ((n: number) => number); + /** Opacity multiplier applied to colors (1 = full, <1 = dimmed). */ + dim: number; +} + +/** Normal top-level ring (game: innerRadius 40, arcWidth 55). */ +const RING_NORMAL: RingConfig = { + outerR: 95, + innerR: 40, + iconHalf: (n) => (n <= 4 ? 20 : n <= 6 ? 17 : 14), + dim: 1.0, +}; + +/** Submenu active ring (game: innerRadius 75, arcWidth 65). */ +const RING_SUBMENU: RingConfig = { + outerR: 140, + innerR: 75, + iconHalf: (n) => (n <= 4 ? 22 : n <= 6 ? 18 : 14), + dim: 1.0, +}; + +/** Parent ring when submenu is open (game: scales to 0.65). */ +const RING_PARENT: RingConfig = { + outerR: 70, + innerR: 32, + iconHalf: 12, + dim: 0.5, +}; +const MAX_SEGMENTS = 8; + +/** Hit-test return value for the center button. */ +export const CENTER_INDEX = -2; + +const BACK_ITEM: RadialMenuItem = { + id: "__back__", + icon: "back-icon", + color: [0.45, 0.45, 0.45], + enabled: true, +}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function buildEmojiMap(): Map { + const map = new Map(); + const emojis = (emojiAtlasMeta as { emojis: Record }).emojis; + for (const [key, idx] of Object.entries(emojis)) { + map.set(key, idx); + } + return map; +} + +// --------------------------------------------------------------------------- +// RadialMenuPass +// --------------------------------------------------------------------------- + +export class RadialMenuPass { + private gl: WebGL2RenderingContext; + + // Programs + private arcProg: WebGLProgram; + private iconProg: WebGLProgram; + private vao: WebGLVertexArrayObject; + + // Arc uniform locations + private arcU: { + anchor: WebGLUniformLocation; + viewport: WebGLUniformLocation; + outerR: WebGLUniformLocation; + innerR: WebGLUniformLocation; + segCount: WebGLUniformLocation; + hoveredSeg: WebGLUniformLocation; + segColors: WebGLUniformLocation; + hasCenterBtn: WebGLUniformLocation; + centerColor: WebGLUniformLocation; + centerHovered: WebGLUniformLocation; + }; + + // Icon uniform locations + private iconU: { + anchor: WebGLUniformLocation; + viewport: WebGLUniformLocation; + outerR: WebGLUniformLocation; + innerR: WebGLUniformLocation; + segCount: WebGLUniformLocation; + iconHalf: WebGLUniformLocation; + emojiIndices: WebGLUniformLocation; + centerEmojiIdx: WebGLUniformLocation; + segOpacity: WebGLUniformLocation; + emojiAtlas: WebGLUniformLocation; + emojiCell: WebGLUniformLocation; + emojiCols: WebGLUniformLocation; + emojiAtlasW: WebGLUniformLocation; + emojiAtlasH: WebGLUniformLocation; + }; + + // Emoji + icon atlas + private emojiTex: WebGLTexture | null = null; + private emojiReady = false; + private emojiMap: Map; + private atlasImg: HTMLImageElement | null = null; + private pendingIcons: { key: string; img: CanvasImageSource }[] = []; + + // ---- State ---- + private visible = false; + private anchorX = 0; + private anchorY = 0; + private items: RadialMenuItem[] = []; + private centerItem: RadialMenuItem | null = null; + private hoveredIndex = -1; // -1 = none, 0..n-1 = segment, CENTER_INDEX = center + + // Submenu (one level) + private _inSubmenu = false; + private savedItems: RadialMenuItem[] = []; + private savedCenterItem: RadialMenuItem | null = null; + + constructor(gl: WebGL2RenderingContext) { + this.gl = gl; + this.emojiMap = buildEmojiMap(); + + // Shared quad VAO + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + + // Arc program + this.arcProg = createProgram(gl, arcVertSrc, arcFragSrc); + this.arcU = { + anchor: gl.getUniformLocation(this.arcProg, "uAnchor")!, + viewport: gl.getUniformLocation(this.arcProg, "uViewport")!, + outerR: gl.getUniformLocation(this.arcProg, "uOuterR")!, + innerR: gl.getUniformLocation(this.arcProg, "uInnerR")!, + segCount: gl.getUniformLocation(this.arcProg, "uSegCount")!, + hoveredSeg: gl.getUniformLocation(this.arcProg, "uHoveredSeg")!, + segColors: gl.getUniformLocation(this.arcProg, "uSegColors")!, + hasCenterBtn: gl.getUniformLocation(this.arcProg, "uHasCenterBtn")!, + centerColor: gl.getUniformLocation(this.arcProg, "uCenterColor")!, + centerHovered: gl.getUniformLocation(this.arcProg, "uCenterHovered")!, + }; + + // Icon program + this.iconProg = createProgram(gl, iconVertSrc, iconFragSrc); + gl.useProgram(this.iconProg); + gl.uniform1i(gl.getUniformLocation(this.iconProg, "uEmojiAtlas"), 0); + const em = emojiAtlasMeta as { + width: number; + height: number; + cellSize: number; + cols: number; + }; + gl.uniform1f( + gl.getUniformLocation(this.iconProg, "uEmojiCell")!, + em.cellSize, + ); + gl.uniform1f(gl.getUniformLocation(this.iconProg, "uEmojiCols")!, em.cols); + gl.uniform1f( + gl.getUniformLocation(this.iconProg, "uEmojiAtlasW")!, + em.width, + ); + gl.uniform1f( + gl.getUniformLocation(this.iconProg, "uEmojiAtlasH")!, + em.height, + ); + + this.iconU = { + anchor: gl.getUniformLocation(this.iconProg, "uAnchor")!, + viewport: gl.getUniformLocation(this.iconProg, "uViewport")!, + outerR: gl.getUniformLocation(this.iconProg, "uOuterR")!, + innerR: gl.getUniformLocation(this.iconProg, "uInnerR")!, + segCount: gl.getUniformLocation(this.iconProg, "uSegCount")!, + iconHalf: gl.getUniformLocation(this.iconProg, "uIconHalf")!, + emojiIndices: gl.getUniformLocation(this.iconProg, "uEmojiIndices")!, + centerEmojiIdx: gl.getUniformLocation(this.iconProg, "uCenterEmojiIdx")!, + segOpacity: gl.getUniformLocation(this.iconProg, "uSegOpacity")!, + emojiAtlas: gl.getUniformLocation(this.iconProg, "uEmojiAtlas")!, + emojiCell: gl.getUniformLocation(this.iconProg, "uEmojiCell")!, + emojiCols: gl.getUniformLocation(this.iconProg, "uEmojiCols")!, + emojiAtlasW: gl.getUniformLocation(this.iconProg, "uEmojiAtlasW")!, + emojiAtlasH: gl.getUniformLocation(this.iconProg, "uEmojiAtlasH")!, + }; + + this.loadEmojiAtlas(); + } + + private loadEmojiAtlas(): void { + const img = new Image(); + img.onload = () => { + this.atlasImg = img; + this.rebuildAtlasTexture(); + }; + img.src = emojiAtlasUrl; + } + + /** + * Register additional icon images to append to the atlas texture. + * Call from the adapter after loading game SVG icons. + */ + registerIcons(icons: { key: string; img: CanvasImageSource }[]): void { + this.pendingIcons = icons; + if (this.atlasImg) this.rebuildAtlasTexture(); + } + + private rebuildAtlasTexture(): void { + if (!this.atlasImg) return; + + const gl = this.gl; + const meta = emojiAtlasMeta as { + width: number; + height: number; + cellSize: number; + cols: number; + emojis: Record; + }; + const baseCount = Object.keys(meta.emojis).length; + const totalCount = baseCount + this.pendingIcons.length; + const rows = Math.ceil(totalCount / meta.cols); + const height = Math.max(meta.height, rows * meta.cellSize); + + const canvas = document.createElement("canvas"); + canvas.width = meta.width; + canvas.height = height; + const ctx = canvas.getContext("2d")!; + + // Draw existing emoji atlas + ctx.drawImage(this.atlasImg, 0, 0); + + // Append extra icons into new cells (preserving aspect ratio) + // Minimal padding — SVGs are already clean vectors, maximize resolution + const pad = Math.floor(meta.cellSize * 0.04); + const size = meta.cellSize - pad * 2; + for (let i = 0; i < this.pendingIcons.length; i++) { + const idx = baseCount + i; + const col = idx % meta.cols; + const row = Math.floor(idx / meta.cols); + const img = this.pendingIcons[i].img; + const nw = (img as HTMLImageElement).naturalWidth || size; + const nh = (img as HTMLImageElement).naturalHeight || size; + const aspect = nw / nh; + let dw = size, + dh = size; + if (aspect > 1) dh = size / aspect; + else dw = size * aspect; + const ox = (size - dw) / 2; + const oy = (size - dh) / 2; + ctx.drawImage( + img, + col * meta.cellSize + pad + ox, + row * meta.cellSize + pad + oy, + dw, + dh, + ); + this.emojiMap.set(this.pendingIcons[i].key, idx); + } + + // Upload texture + this.emojiTex ??= gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, this.emojiTex); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + gl.LINEAR_MIPMAP_LINEAR, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); + gl.generateMipmap(gl.TEXTURE_2D); + this.emojiReady = true; + + // Update atlas height uniform (texture may be taller now) + gl.useProgram(this.iconProg); + gl.uniform1f(this.iconU.emojiAtlasH, height); + } + + resolveEmoji(icon: string): number { + return this.emojiMap.get(icon) ?? -1; + } + + // --------------------------------------------------------------------------- + // Public API + // --------------------------------------------------------------------------- + + show( + anchorX: number, + anchorY: number, + items: RadialMenuItem[], + centerItem?: RadialMenuItem, + ): void { + this.visible = true; + this.anchorX = anchorX; + this.anchorY = anchorY; + this.items = items.slice(0, MAX_SEGMENTS); + this.centerItem = centerItem ?? null; + // Cursor is at the anchor — center button starts hovered + this.hoveredIndex = this.centerItem ? CENTER_INDEX : -1; + this._inSubmenu = false; + this.savedItems = []; + this.savedCenterItem = null; + } + + openSubMenu(subItems: RadialMenuItem[]): void { + this.savedItems = this.items; + this.savedCenterItem = this.centerItem; + this.items = subItems.slice(0, MAX_SEGMENTS); + this.centerItem = BACK_ITEM; + this._inSubmenu = true; + this.hoveredIndex = -1; + } + + goBack(): void { + if (!this._inSubmenu) return; + this.items = this.savedItems; + this.centerItem = this.savedCenterItem; + this._inSubmenu = false; + this.savedItems = []; + this.savedCenterItem = null; + this.hoveredIndex = -1; + } + + hide(): void { + this.visible = false; + this.hoveredIndex = -1; + this._inSubmenu = false; + this.savedItems = []; + this.savedCenterItem = null; + } + + setHover(index: number): void { + this.hoveredIndex = index; + } + + get isVisible(): boolean { + return this.visible; + } + get inSubmenu(): boolean { + return this._inSubmenu; + } + getItems(): readonly RadialMenuItem[] { + return this.items; + } + getCenterItem(): RadialMenuItem | null { + return this.centerItem; + } + + /** Look up an item by hit-test index. */ + getItemAt(index: number): RadialMenuItem | null { + if (index === CENTER_INDEX) return this.centerItem; + if (index >= 0 && index < this.items.length) return this.items[index]; + return null; + } + + // --------------------------------------------------------------------------- + // Hit testing + // --------------------------------------------------------------------------- + + hitTest(screenX: number, screenY: number): number { + if (!this.visible) return -1; + const dx = screenX - this.anchorX; + const dy = screenY - this.anchorY; + const dist = Math.sqrt(dx * dx + dy * dy); + + const active = this._inSubmenu ? RING_SUBMENU : RING_NORMAL; + const centerR = this._inSubmenu ? RING_PARENT.innerR : RING_NORMAL.innerR; + const ringInner = active.innerR; + const ringOuter = active.outerR; + + // Center button + if (dist < centerR) return this.centerItem ? CENTER_INDEX : -1; + + // Gap / parent ring zone (non-interactive) + if (dist < ringInner) return -1; + + // Active ring + if (dist > ringOuter || this.items.length === 0) return -1; + + let angle = Math.atan2(dx, -dy); // 0 = top, CW positive + if (angle < 0) angle += Math.PI * 2; + const n = this.items.length; + const segArc = (Math.PI * 2) / n; + // Rotate so first segment is centered at top (game: startAngle = -π/n) + const shifted = (angle + Math.PI / n) % (Math.PI * 2); + return Math.min(Math.floor(shifted / segArc), n - 1); + } + + // --------------------------------------------------------------------------- + // Rendering + // --------------------------------------------------------------------------- + + draw(): void { + if (!this.visible) return; + if (this.items.length === 0 && !this.centerItem) return; + + const gl = this.gl; + const dpr = window.devicePixelRatio || 1; + const vw = gl.drawingBufferWidth; + const vh = gl.drawingBufferHeight; + const ax = this.anchorX * dpr; + const ay = this.anchorY * dpr; + + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.bindVertexArray(this.vao); + + // Parent ring (dimmed, non-interactive) — drawn first so active ring overlays + if (this._inSubmenu && this.savedItems.length > 0) { + const p = RING_PARENT; + this.drawRing( + ax, + ay, + vw, + vh, + p, + this.savedItems, + -1, + BACK_ITEM, + this.hoveredIndex === CENTER_INDEX, + ); + } + + // Active ring — expands when in submenu + const active = this._inSubmenu ? RING_SUBMENU : RING_NORMAL; + this.drawRing( + ax, + ay, + vw, + vh, + active, + this.items, + this.hoveredIndex >= 0 ? this.hoveredIndex : -1, + this._inSubmenu ? null : this.centerItem, + !this._inSubmenu && this.hoveredIndex === CENTER_INDEX, + ); + } + + /** Draw a single ring (arcs + icons) using a RingConfig. */ + private drawRing( + ax: number, + ay: number, + vw: number, + vh: number, + cfg: RingConfig, + items: readonly RadialMenuItem[], + hoveredSeg: number, + centerItem: RadialMenuItem | null, + centerHovered: boolean, + ): void { + const gl = this.gl; + const dpr = window.devicePixelRatio || 1; + const n = items.length; + const hasCenter = centerItem !== null; + const outerR = cfg.outerR * dpr; + const innerFrac = cfg.innerR / cfg.outerR; + const dim = cfg.dim; + const ih = + typeof cfg.iconHalf === "function" ? cfg.iconHalf(n) : cfg.iconHalf; + const iconHalf = ih * dpr; + + // --- Arcs --- + gl.useProgram(this.arcProg); + gl.uniform2f(this.arcU.anchor, ax, ay); + gl.uniform2f(this.arcU.viewport, vw, vh); + gl.uniform1f(this.arcU.outerR, outerR); + gl.uniform1f(this.arcU.innerR, innerFrac); + gl.uniform1i(this.arcU.segCount, n); + gl.uniform1i(this.arcU.hoveredSeg, hoveredSeg); + + gl.uniform1i(this.arcU.hasCenterBtn, hasCenter ? 1 : 0); + if (hasCenter) { + const cc = centerItem.color; + gl.uniform3f( + this.arcU.centerColor, + cc[0] * dim, + cc[1] * dim, + cc[2] * dim, + ); + gl.uniform1i(this.arcU.centerHovered, centerHovered ? 1 : 0); + } + + const colors = new Float32Array(MAX_SEGMENTS * 4); + for (let i = 0; i < n; i++) { + const c = items[i].color; + colors[i * 4 + 0] = c[0] * dim; + colors[i * 4 + 1] = c[1] * dim; + colors[i * 4 + 2] = c[2] * dim; + colors[i * 4 + 3] = items[i].enabled ? 1 : 0; + } + gl.uniform4fv(this.arcU.segColors, colors); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + // --- Icons --- + if (!this.emojiReady || (n === 0 && !hasCenter)) return; + + gl.useProgram(this.iconProg); + gl.uniform2f(this.iconU.anchor, ax, ay); + gl.uniform2f(this.iconU.viewport, vw, vh); + gl.uniform1f(this.iconU.outerR, outerR); + gl.uniform1f(this.iconU.innerR, innerFrac); + gl.uniform1i(this.iconU.segCount, n); + gl.uniform1f(this.iconU.iconHalf, iconHalf); + + const indices = new Float32Array(MAX_SEGMENTS); + const opacities = new Float32Array(MAX_SEGMENTS); + indices.fill(-1); + opacities.fill(1); + for (let i = 0; i < n; i++) { + indices[i] = this.resolveEmoji(items[i].icon); + opacities[i] = items[i].enabled ? 1.0 : 0.3; + } + gl.uniform1fv(this.iconU.emojiIndices, indices); + gl.uniform1fv(this.iconU.segOpacity, opacities); + + const centerIdx = hasCenter ? this.resolveEmoji(centerItem.icon) : -1; + gl.uniform1f(this.iconU.centerEmojiIdx, centerIdx); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.emojiTex!); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, n + 1); + } + + // --------------------------------------------------------------------------- + // Lifecycle + // --------------------------------------------------------------------------- + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.arcProg); + gl.deleteProgram(this.iconProg); + gl.deleteVertexArray(this.vao); + if (this.emojiTex) gl.deleteTexture(this.emojiTex); + } +} diff --git a/src/client/render/gl/passes/railroad-pass.ts b/src/client/render/gl/passes/railroad-pass.ts new file mode 100644 index 0000000000..1892f8d663 --- /dev/null +++ b/src/client/render/gl/passes/railroad-pass.ts @@ -0,0 +1,340 @@ +/** + * RailroadPass — GPU railroad overlay rendering. + * + * Renders railroad tracks as a fullscreen quad pass, reading rail orientation + * from an R8UI texture. Two LOD modes: detailed 3×3 sub-grid sprites at high + * zoom, screen-space anti-aliased lines at medium zoom. Hidden below minimum + * zoom threshold. + * + * Also renders ghost railroad paths (semi-transparent) for build-mode preview. + * + * Data flow: + * Uint8Array railroadState → R8UI texture (rail type per tile, 0=none, 1-6=type) + * GhostPreviewData → R8UI ghost texture (ghost rail paths) + * R8UI terrainTex → water detection for bridge rendering (shader neighbor lookup) + * R16UI tileTex (shared) → owner lookup for rail color + * RGBA32F paletteTex → player color lookup + */ + +import type { GhostPreviewData } from "../../types"; +import type { RenderSettings } from "../render-settings"; +import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; +import railroadFragSrc from "../shaders/railroad/railroad.frag.glsl?raw"; +import { getPaletteSize } from "../utils/color-utils"; +import { + createMapQuad, + createProgram, + createTexture2D, + shaderSrc, +} from "../utils/gl-utils"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +// --------------------------------------------------------------------------- +// Rail orientation (0-5) → texture value (1-6, 0=none) +// --------------------------------------------------------------------------- + +const VERTICAL = 0; +const HORIZONTAL = 1; +const TOP_LEFT = 2; +const TOP_RIGHT = 3; +const BOTTOM_LEFT = 4; +const BOTTOM_RIGHT = 5; + +function railExtremity(tile: number, next: number, w: number): number { + const dx = (next % w) - (tile % w); + const dy = (next - (next % w)) / w - (tile - (tile % w)) / w; + if (dx === 0) return VERTICAL; + if (dy === 0) return HORIZONTAL; + return VERTICAL; +} + +function railDirection( + prev: number, + cur: number, + next: number, + w: number, +): number { + const x1 = prev % w, + y1 = (prev - x1) / w; + const x2 = cur % w, + y2 = (cur - x2) / w; + const x3 = next % w, + y3 = (next - x3) / w; + const dx1 = x2 - x1, + dy1 = y2 - y1; + const dx2 = x3 - x2, + dy2 = y3 - y2; + if (dx1 === dx2 && dy1 === dy2) { + return dx1 !== 0 ? HORIZONTAL : VERTICAL; + } + if ((dx1 === 0 && dx2 !== 0) || (dx1 !== 0 && dx2 === 0)) { + if (dx1 === 0 && dx2 === 1 && dy1 === -1) return BOTTOM_RIGHT; + if (dx1 === 0 && dx2 === -1 && dy1 === -1) return BOTTOM_LEFT; + if (dx1 === 0 && dx2 === 1 && dy1 === 1) return TOP_RIGHT; + if (dx1 === 0 && dx2 === -1 && dy1 === 1) return TOP_LEFT; + if (dx1 === 1 && dx2 === 0 && dy2 === -1) return TOP_LEFT; + if (dx1 === -1 && dx2 === 0 && dy2 === -1) return TOP_RIGHT; + if (dx1 === 1 && dx2 === 0 && dy2 === 1) return BOTTOM_LEFT; + if (dx1 === -1 && dx2 === 0 && dy2 === 1) return BOTTOM_RIGHT; + } + return VERTICAL; +} + +// --------------------------------------------------------------------------- +// RailroadPass +// --------------------------------------------------------------------------- + +export class RailroadPass { + private program: WebGLProgram; + private railroadTex: WebGLTexture; + private ghostRailTex: WebGLTexture; + private tileTex: WebGLTexture; + private paletteTex: WebGLTexture; + private terrainTex: WebGLTexture; + private vao: WebGLVertexArrayObject; + + private uCamera: WebGLUniformLocation; + private uMapSize: WebGLUniformLocation; + private uZoom: WebGLUniformLocation; + private uRailDetailZoom: WebGLUniformLocation; + private uRailAlpha: WebGLUniformLocation; + private uGhostOwnerID: WebGLUniformLocation; + + private mapW: number; + private mapH: number; + private settings: RenderSettings; + + private cpuRailroadState: Uint8Array; + private railroadDirty = false; + + private cpuGhostRailState: Uint8Array; + private ghostRailDirty = false; + private ghostOwnerID = 0; + + constructor( + private gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + paletteTex: WebGLTexture, + terrainBytes: Uint8Array, + settings: RenderSettings, + ) { + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.paletteTex = paletteTex; + this.settings = settings; + this.cpuRailroadState = new Uint8Array(mapW * mapH); + this.cpuGhostRailState = new Uint8Array(mapW * mapH); + + this.program = createProgram( + gl, + overlayVertSrc, + shaderSrc(railroadFragSrc, { + PALETTE_SIZE: getPaletteSize(), + ...TILE_DEFINES, + }), + ); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uZoom = gl.getUniformLocation(this.program, "uZoom")!; + this.uRailDetailZoom = gl.getUniformLocation( + this.program, + "uRailDetailZoom", + )!; + this.uRailAlpha = gl.getUniformLocation(this.program, "uRailAlpha")!; + this.uGhostOwnerID = gl.getUniformLocation(this.program, "uGhostOwnerID")!; + + // Texture unit bindings + ghost defaults + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uRailroadTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uPalette"), 2); + gl.uniform1i(gl.getUniformLocation(this.program, "uTerrainTex"), 3); + gl.uniform1i(gl.getUniformLocation(this.program, "uGhostRailTex"), 4); + gl.uniform1f(this.uGhostOwnerID, 0); + + // R8UI terrain texture (static, uploaded once for bridge detection) + this.terrainTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R8UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_BYTE, + data: terrainBytes, + filter: gl.NEAREST, + }); + + // R8UI railroad texture + this.railroadTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R8UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_BYTE, + data: this.cpuRailroadState, + filter: gl.NEAREST, + }); + + // R8UI ghost railroad texture (same format, ghost paths only) + this.ghostRailTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R8UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_BYTE, + data: this.cpuGhostRailState, + filter: gl.NEAREST, + }); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + uploadRailroadState(railroadState: Uint8Array): void { + this.cpuRailroadState.set(railroadState); + this.railroadDirty = true; + } + + updateGhostPreview(data: GhostPreviewData | null): void { + this.cpuGhostRailState.fill(0); + + if (data) { + const maxRef = this.mapW * this.mapH; + + // Ghost rail paths (1-6 = orientation) + for (const path of data.ghostRailPaths) { + if (path.length === 0) continue; + const tiles = this.computePathOrientations(path); + for (const t of tiles) { + if (t.ref >= 0 && t.ref < maxRef) { + this.cpuGhostRailState[t.ref] = t.type + 1; + } + } + } + + // Overlapping railroad highlights (7 = green highlight marker) + // overlappingRailroads contains resolved tile refs (not rail IDs) + for (const ref of data.overlappingRailroads) { + if (ref >= 0 && ref < maxRef) { + this.cpuGhostRailState[ref] = 7; + } + } + + this.ghostOwnerID = data.ownerID; + } else { + this.ghostOwnerID = 0; + } + + this.ghostRailDirty = true; + } + + /** Draw the railroad overlay. Must be called with alpha blending enabled. */ + draw(cameraMatrix: Float32Array, zoom: number): void { + const gl = this.gl; + const rs = this.settings.railroad; + + // Skip entirely when below minimum zoom + if (zoom < rs.railMinZoom) return; + + // Flush CPU railroad state → GPU + if (this.railroadDirty) { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.railroadTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + this.mapW, + this.mapH, + gl.RED_INTEGER, + gl.UNSIGNED_BYTE, + this.cpuRailroadState, + ); + this.railroadDirty = false; + } + + // Flush ghost railroad state → GPU + if (this.ghostRailDirty) { + gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this.ghostRailTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + this.mapW, + this.mapH, + gl.RED_INTEGER, + gl.UNSIGNED_BYTE, + this.cpuGhostRailState, + ); + this.ghostRailDirty = false; + } + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1f(this.uZoom, zoom); + gl.uniform1f(this.uRailDetailZoom, rs.railDetailZoom); + gl.uniform1f(this.uRailAlpha, rs.railAlpha); + gl.uniform1f(this.uGhostOwnerID, this.ghostOwnerID); + + // Bind textures: 0=railroad, 1=tile, 2=palette, 3=terrain, 4=ghostRail + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.railroadTex); + + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + + gl.activeTexture(gl.TEXTURE3); + gl.bindTexture(gl.TEXTURE_2D, this.terrainTex); + + gl.activeTexture(gl.TEXTURE4); + gl.bindTexture(gl.TEXTURE_2D, this.ghostRailTex); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + // ---- Rail orientation computation ---- + + private computePathOrientations( + tileRefs: number[], + ): Array<{ ref: number; type: number }> { + if (tileRefs.length === 0) return []; + if (tileRefs.length === 1) return [{ ref: tileRefs[0], type: VERTICAL }]; + const w = this.mapW; + const result: Array<{ ref: number; type: number }> = []; + result.push({ + ref: tileRefs[0], + type: railExtremity(tileRefs[0], tileRefs[1], w), + }); + for (let i = 1; i < tileRefs.length - 1; i++) { + result.push({ + ref: tileRefs[i], + type: railDirection(tileRefs[i - 1], tileRefs[i], tileRefs[i + 1], w), + }); + } + const last = tileRefs.length - 1; + result.push({ + ref: tileRefs[last], + type: railExtremity(tileRefs[last], tileRefs[last - 1], w), + }); + return result; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteTexture(this.railroadTex); + gl.deleteTexture(this.ghostRailTex); + gl.deleteTexture(this.terrainTex); + // Don't delete tileTex or paletteTex — shared with other passes + } +} diff --git a/src/client/render/gl/passes/range-circle-pass.ts b/src/client/render/gl/passes/range-circle-pass.ts new file mode 100644 index 0000000000..33241e6cc6 --- /dev/null +++ b/src/client/render/gl/passes/range-circle-pass.ts @@ -0,0 +1,79 @@ +/** + * RangeCirclePass — draws a translucent white circle showing the effective + * range of a structure during build-mode ghost preview. + * + * Single quad with circle SDF in the fragment shader. + * Active only when a ghost preview with rangeRadius > 0 is set. + */ + +import type { GhostPreviewData } from "../../types"; +import { createProgram } from "../utils/gl-utils"; + +import fragSrc from "../shaders/range-circle/range-circle.frag.glsl?raw"; +import vertSrc from "../shaders/range-circle/range-circle.vert.glsl?raw"; + +export class RangeCirclePass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + + private uCamera: WebGLUniformLocation; + private uCenter: WebGLUniformLocation; + private uRadius: WebGLUniformLocation; + + private centerX = 0; + private centerY = 0; + private radius = 0; + + constructor(gl: WebGL2RenderingContext) { + this.gl = gl; + this.program = createProgram(gl, vertSrc, fragSrc); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uCenter = gl.getUniformLocation(this.program, "uCenter")!; + this.uRadius = gl.getUniformLocation(this.program, "uRadius")!; + + // Unit quad [0,1] + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + } + + updateGhostPreview(data: GhostPreviewData | null): void { + if (data && data.rangeRadius > 0) { + this.centerX = data.tileX; + this.centerY = data.tileY; + this.radius = data.rangeRadius; + } else { + this.radius = 0; + } + } + + draw(cameraMatrix: Float32Array): void { + if (this.radius <= 0) return; + + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uCenter, this.centerX, this.centerY); + gl.uniform1f(this.uRadius, this.radius); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/sam-radius-pass.ts b/src/client/render/gl/passes/sam-radius-pass.ts new file mode 100644 index 0000000000..b5cf0cc980 --- /dev/null +++ b/src/client/render/gl/passes/sam-radius-pass.ts @@ -0,0 +1,396 @@ +/** + * SAMRadiusPass — renders rotating dashed circles around SAM launchers + * when the player is in build mode (ghost preview active). + * + * Allied SAM ranges are merged via circle union: overlapping circles from + * the same alliance group show as a single combined shape rather than + * overlapping rings. Each circle's visible (uncovered) arcs are emitted + * as separate instances. + * + * Colors by ownership relationship: + * self → green (0, 1, 0) + * ally → yellow (1, 1, 0) + * enemy → red (1, 0, 0) + */ + +import type { UnitState } from "../../types"; +import { UT_SAM_LAUNCHER } from "../../types"; +import { DynamicInstanceBuffer } from "../dynamic-buffer"; +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; +import { samRange } from "../utils/nuke-trajectory"; + +import fragSrc from "../shaders/sam-radius/sam-radius.frag.glsl?raw"; +import vertSrc from "../shaders/sam-radius/sam-radius.vert.glsl?raw"; + +const TWO_PI = Math.PI * 2; +const EPS = 1e-9; + +// Per-instance: x, y, radius, r, g, b, arcStart, arcEnd +const FLOATS_PER_INSTANCE = 8; + +// Relationship colors +const COLOR_SELF = [0, 1, 0]; // green +const COLOR_ALLY = [1, 1, 0]; // yellow +const COLOR_ENEMY = [1, 0, 0]; // red + +interface SAMCircle { + x: number; + y: number; + radius: number; + color: number[]; + group: number; // alliance group: 0 = friendly, 1 = enemy +} + +type Interval = [number, number]; + +// --------------------------------------------------------------------------- +// Circle union geometry +// --------------------------------------------------------------------------- + +function normalizeAngle(a: number): number { + while (a < 0) a += TWO_PI; + while (a >= TWO_PI) a -= TWO_PI; + return a; +} + +function mergeIntervals(intervals: Interval[]): Interval[] { + if (intervals.length === 0) return []; + + // Split wrapping intervals, then merge + const flat: Interval[] = []; + for (const [s, e] of intervals) { + const ns = normalizeAngle(s); + const ne = normalizeAngle(e); + if (ne < ns) { + flat.push([ns, TWO_PI]); + flat.push([0, ne]); + } else { + flat.push([ns, ne]); + } + } + flat.sort((a, b) => a[0] - b[0]); + + const merged: Interval[] = []; + let cur: Interval = [flat[0][0], flat[0][1]]; + for (let i = 1; i < flat.length; i++) { + const it = flat[i]; + if (it[0] <= cur[1] + EPS) { + cur[1] = Math.max(cur[1], it[1]); + } else { + merged.push(cur); + cur = [it[0], it[1]]; + } + } + merged.push(cur); + return merged; +} + +/** Compute the uncovered arc intervals for circle `a` given all circles. */ +function computeUncoveredArcs(a: SAMCircle, circles: SAMCircle[]): Interval[] { + const covered: Interval[] = []; + + for (const b of circles) { + if (a === b) continue; + if (a.group !== b.group) continue; + + const dx = b.x - a.x; + const dy = b.y - a.y; + const d = Math.hypot(dx, dy); + + // a fully inside b → no visible arcs + if (d + a.radius <= b.radius + EPS) return []; + + // No overlap + if (d >= a.radius + b.radius - EPS) continue; + + // Coincident centers + if (d <= EPS) { + if (b.radius >= a.radius) return []; + continue; + } + + // Angular span on a covered by b (law of cosines) + const cosPhi = + (a.radius * a.radius + d * d - b.radius * b.radius) / (2 * a.radius * d); + const phi = Math.acos(Math.max(-1, Math.min(1, cosPhi))); + const theta = Math.atan2(dy, dx); + covered.push([theta - phi, theta + phi]); + } + + const merged = mergeIntervals(covered); + + // Subtract covered from [0, 2π) + if (merged.length === 0) return [[0, TWO_PI]]; + + const uncovered: Interval[] = []; + let cursor = 0; + for (const [s, e] of merged) { + if (s > cursor + EPS) uncovered.push([cursor, s]); + cursor = Math.max(cursor, e); + } + if (cursor < TWO_PI - EPS) uncovered.push([cursor, TWO_PI]); + + return uncovered; +} + +// --------------------------------------------------------------------------- +// Pass +// --------------------------------------------------------------------------- + +export class SAMRadiusPass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + + private uCamera: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uOutline: WebGLUniformLocation; + private uStrokeWidth: WebGLUniformLocation; + private uDashLen: WebGLUniformLocation; + private uGapLen: WebGLUniformLocation; + private uRotationSpeed: WebGLUniformLocation; + private uAlpha: WebGLUniformLocation; + private uOutlineWidth: WebGLUniformLocation; + private uOutlineSoftness: WebGLUniformLocation; + + private settings: RenderSettings; + private instanceCount = 0; + private visible = false; + private mapW = 0; + private startTime = performance.now(); + + private localPlayerID = 0; + private allies = new Set(); + + // Owner-color mode fields + private paletteData: Float32Array | null = null; + private colorMode: "perspective" | "owner" = "perspective"; + private allianceClusters: Map = new Map(); + private lastStructures: Map | null = null; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + settings: RenderSettings, + ) { + this.gl = gl; + this.mapW = mapW; + this.settings = settings; + this.program = createProgram(gl, vertSrc, fragSrc); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uOutline = gl.getUniformLocation(this.program, "uOutline")!; + this.uStrokeWidth = gl.getUniformLocation(this.program, "uStrokeWidth")!; + this.uDashLen = gl.getUniformLocation(this.program, "uDashLen")!; + this.uGapLen = gl.getUniformLocation(this.program, "uGapLen")!; + this.uRotationSpeed = gl.getUniformLocation( + this.program, + "uRotationSpeed", + )!; + this.uAlpha = gl.getUniformLocation(this.program, "uAlpha")!; + this.uOutlineWidth = gl.getUniformLocation(this.program, "uOutlineWidth")!; + this.uOutlineSoftness = gl.getUniformLocation( + this.program, + "uOutlineSoftness", + )!; + + // VAO + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + // Attribute 0: unit quad [0,1] + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Instance buffer: [x, y, radius, r, g, b, arcStart, arcEnd] + const glBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer( + gl, + glBuf, + 64, + FLOATS_PER_INSTANCE, + ); + + gl.bindBuffer(gl.ARRAY_BUFFER, glBuf); + const stride = FLOATS_PER_INSTANCE * 4; + + // Attribute 1: per-instance vec3 (x, y, radius) + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 3, gl.FLOAT, false, stride, 0); + gl.vertexAttribDivisor(1, 1); + + // Attribute 2: per-instance vec3 (r, g, b) + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 3, gl.FLOAT, false, stride, 12); + gl.vertexAttribDivisor(2, 1); + + // Attribute 3: per-instance vec2 (arcStart, arcEnd) + gl.enableVertexAttribArray(3); + gl.vertexAttribPointer(3, 2, gl.FLOAT, false, stride, 24); + gl.vertexAttribDivisor(3, 1); + + gl.bindVertexArray(null); + } + + /** Set the local player's ID (from ghost preview ownerID). */ + setLocalPlayer(id: number): void { + if (id === this.localPlayerID) return; + this.localPlayerID = id; + this.rebuild(); + } + + /** Update ally set (player smallIDs allied with local player). */ + setAllies(allies: Set): void { + this.allies = allies; + this.rebuild(); + } + + setPaletteData(data: Float32Array): void { + this.paletteData = data; + } + + setColorMode(mode: "perspective" | "owner"): void { + if (mode === this.colorMode) return; + this.colorMode = mode; + this.rebuild(); + } + + setAllianceClusters(clusters: Map): void { + this.allianceClusters = clusters; + } + + private rebuild(): void { + if (this.lastStructures) this.updateStructures(this.lastStructures); + } + + /** Call with current structures to update SAM positions/radii/colors. */ + updateStructures(structures: Map): void { + this.lastStructures = structures; + const w = this.mapW; + const ownerMode = this.colorMode === "owner"; + + // 1. Collect SAM circles + const circles: SAMCircle[] = []; + for (const u of structures.values()) { + if (u.unitType !== UT_SAM_LAUNCHER) continue; + if (!u.isActive) continue; + + const x = u.pos % w; + const y = (u.pos - x) / w; + + let color: number[]; + let group: number; + + if (ownerMode && this.paletteData) { + // Owner-colored: palette color, alliance-cluster-based merging + const off = u.ownerID * 4; + color = [ + this.paletteData[off], + this.paletteData[off + 1], + this.paletteData[off + 2], + ]; + group = this.allianceClusters.get(u.ownerID) ?? u.ownerID; + } else { + // Perspective: self/ally/enemy colors, binary group + const isFriendly = + u.ownerID === this.localPlayerID || this.allies.has(u.ownerID); + color = + u.ownerID === this.localPlayerID + ? COLOR_SELF + : this.allies.has(u.ownerID) + ? COLOR_ALLY + : COLOR_ENEMY; + group = isFriendly ? 0 : 1; + } + + circles.push({ + x, + y, + radius: samRange(u.level), + color, + group, + }); + } + + // 2. Compute circle unions → uncovered arcs per circle + let count = 0; + for (const c of circles) { + const arcs = computeUncoveredArcs(c, circles); + + for (const [arcStart, arcEnd] of arcs) { + this.instanceBuf.ensureCapacity(count + 1); + + const off = count * FLOATS_PER_INSTANCE; + const data = this.instanceBuf.float32; + data[off + 0] = c.x; + data[off + 1] = c.y; + data[off + 2] = c.radius; + data[off + 3] = c.color[0]; + data[off + 4] = c.color[1]; + data[off + 5] = c.color[2]; + data[off + 6] = arcStart; + data[off + 7] = arcEnd; + count++; + } + } + + this.instanceCount = count; + + if (count > 0) { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + count * FLOATS_PER_INSTANCE, + ); + } + } + + /** Show/hide based on whether build mode is active. */ + setVisible(visible: boolean): void { + this.visible = visible; + } + + draw(cameraMatrix: Float32Array): void { + if (!this.visible || this.instanceCount === 0) return; + + const gl = this.gl; + const time = (performance.now() - this.startTime) / 1000; + + const s = this.settings.samRadius; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTime, time); + gl.uniform1f(this.uOutline, this.colorMode === "owner" ? 1.0 : 0.0); + gl.uniform1f(this.uStrokeWidth, s.strokeWidth); + gl.uniform1f(this.uDashLen, s.dashLen); + gl.uniform1f(this.uGapLen, s.gapLen); + gl.uniform1f(this.uRotationSpeed, s.rotationSpeed); + gl.uniform1f(this.uAlpha, s.alpha); + gl.uniform1f(this.uOutlineWidth, s.outlineWidth); + gl.uniform1f(this.uOutlineSoftness, s.outlineSoftness); + + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/selection-box-pass.ts b/src/client/render/gl/passes/selection-box-pass.ts new file mode 100644 index 0000000000..cf4af16cf9 --- /dev/null +++ b/src/client/render/gl/passes/selection-box-pass.ts @@ -0,0 +1,103 @@ +/** + * SelectionBoxPass — draws a stippled pulsating square border around a + * selected warship, matching the game's native UILayer selection box. + * + * Single quad with tile-space SDF logic in the fragment shader. + * Active only when a unit is selected via setSelectedUnit(). + */ + +import { createProgram } from "../utils/gl-utils"; + +import fragSrc from "../shaders/selection-box/selection-box.frag.glsl?raw"; +import vertSrc from "../shaders/selection-box/selection-box.vert.glsl?raw"; + +/** Half-size of the selection box in tiles (matches game's SELECTION_BOX_SIZE). */ +const HALF_SIZE = 6; + +export class SelectionBoxPass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + + private uCamera: WebGLUniformLocation; + private uCenter: WebGLUniformLocation; + private uHalfSize: WebGLUniformLocation; + private uTime: WebGLUniformLocation; + private uColor: WebGLUniformLocation; + + private active = false; + private centerX = 0; + private centerY = 0; + private colorR = 1; + private colorG = 1; + private colorB = 1; + + constructor(gl: WebGL2RenderingContext) { + this.gl = gl; + this.program = createProgram(gl, vertSrc, fragSrc); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uCenter = gl.getUniformLocation(this.program, "uCenter")!; + this.uHalfSize = gl.getUniformLocation(this.program, "uHalfSize")!; + this.uTime = gl.getUniformLocation(this.program, "uTime")!; + this.uColor = gl.getUniformLocation(this.program, "uColor")!; + + // Unit quad [0,1] + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + gl.bindVertexArray(null); + } + + /** + * Set the selection box center and color. Pass active=false to hide. + */ + update( + active: boolean, + centerX: number, + centerY: number, + r: number, + g: number, + b: number, + ): void { + this.active = active; + this.centerX = centerX; + this.centerY = centerY; + this.colorR = r; + this.colorG = g; + this.colorB = b; + } + + hide(): void { + this.active = false; + } + + draw(cameraMatrix: Float32Array, frameTick: number): void { + if (!this.active) return; + + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uCenter, this.centerX, this.centerY); + gl.uniform1f(this.uHalfSize, HALF_SIZE); + gl.uniform1f(this.uTime, frameTick); + gl.uniform3f(this.uColor, this.colorR, this.colorG, this.colorB); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/spawn-overlay-pass.ts b/src/client/render/gl/passes/spawn-overlay-pass.ts new file mode 100644 index 0000000000..7d9936e126 --- /dev/null +++ b/src/client/render/gl/passes/spawn-overlay-pass.ts @@ -0,0 +1,176 @@ +/** + * SpawnOverlayPass — spawn phase tile highlights + breathing rings. + * + * Active only during spawn phase. Renders: + * 1. Colored highlights on unowned tiles within radius 9 of each human + * player's spawn center (blinks every 5th tick). + * 2. Animated breathing rings around the local player and teammates. + * + * Uses a fullscreen map quad (reuses overlay.vert.glsl) so the fragment + * shader can sample tileTex for ownership and compute distance-based + * effects in tile-space coordinates. + */ + +import type { RenderSettings } from "../render-settings"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; +import spawnFragSrc from "../shaders/spawn-overlay/spawn-overlay.frag.glsl?raw"; + +const MAX_SPAWNS = 32; + +export interface SpawnCenter { + x: number; + y: number; + r: number; + g: number; + b: number; + isSelf: boolean; + isTeammate: boolean; +} + +export class SpawnOverlayPass { + private gl: WebGL2RenderingContext; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; + private tileTex: WebGLTexture; + private settings: RenderSettings["spawnOverlay"]; + + // Uniforms + private uCamera: WebGLUniformLocation; + private uMapSize: WebGLUniformLocation; + private uSpawnCount: WebGLUniformLocation; + private uBreathRadius: WebGLUniformLocation; + private uSpawnA: WebGLUniformLocation; + private uSpawnB: WebGLUniformLocation; + private uHighlightRadiusSq: WebGLUniformLocation; + private uHighlightAlpha: WebGLUniformLocation; + private uSelfRadii: WebGLUniformLocation; + private uMateRadii: WebGLUniformLocation; + private uGradientStops: WebGLUniformLocation; + + private mapW: number; + private mapH: number; + + // State + private active = false; + private centers: SpawnCenter[] = []; + private animTime = 0; + private lastTime = 0; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + settings: RenderSettings["spawnOverlay"], + ) { + this.gl = gl; + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.settings = settings; + + this.program = createProgram( + gl, + overlayVertSrc, + shaderSrc(spawnFragSrc, { MAX_SPAWNS, ...TILE_DEFINES }), + ); + + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uSpawnCount = gl.getUniformLocation(this.program, "uSpawnCount")!; + this.uBreathRadius = gl.getUniformLocation(this.program, "uBreathRadius")!; + this.uSpawnA = gl.getUniformLocation(this.program, "uSpawnA")!; + this.uSpawnB = gl.getUniformLocation(this.program, "uSpawnB")!; + this.uHighlightRadiusSq = gl.getUniformLocation( + this.program, + "uHighlightRadiusSq", + )!; + this.uHighlightAlpha = gl.getUniformLocation( + this.program, + "uHighlightAlpha", + )!; + this.uSelfRadii = gl.getUniformLocation(this.program, "uSelfRadii")!; + this.uMateRadii = gl.getUniformLocation(this.program, "uMateRadii")!; + this.uGradientStops = gl.getUniformLocation( + this.program, + "uGradientStops", + )!; + + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + /** Update spawn overlay state each frame. */ + update(inSpawnPhase: boolean, centers: SpawnCenter[]): void { + this.active = inSpawnPhase && centers.length > 0; + this.centers = centers; + } + + draw(cameraMatrix: Float32Array): void { + if (!this.active) return; + + const gl = this.gl; + const s = this.settings; + const now = performance.now(); + + // Advance animation time + if (this.lastTime > 0) { + this.animTime += (now - this.lastTime) * s.animSpeed; + } + this.lastTime = now; + + const breathRadius = 0.5 + 0.5 * Math.sin(this.animTime); + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1i(this.uSpawnCount, Math.min(this.centers.length, MAX_SPAWNS)); + gl.uniform1f(this.uBreathRadius, breathRadius); + + // Settings-driven uniforms + gl.uniform1f( + this.uHighlightRadiusSq, + s.highlightRadius * s.highlightRadius, + ); + gl.uniform1f(this.uHighlightAlpha, s.highlightAlpha); + gl.uniform4f(this.uSelfRadii, s.selfMinRad, s.selfMaxRad, 0, 0); + gl.uniform4f(this.uMateRadii, s.mateMinRad, s.mateMaxRad, 0, 0); + gl.uniform2f(this.uGradientStops, s.gradientInnerEdge, s.gradientSolidEnd); + + // Upload spawn center data as vec4 arrays + const count = Math.min(this.centers.length, MAX_SPAWNS); + const dataA = new Float32Array(count * 4); + const dataB = new Float32Array(count * 4); + for (let i = 0; i < count; i++) { + const c = this.centers[i]; + dataA[i * 4 + 0] = c.x; + dataA[i * 4 + 1] = c.y; + dataA[i * 4 + 2] = c.r; + dataA[i * 4 + 3] = c.g; + dataB[i * 4 + 0] = c.b; + dataB[i * 4 + 1] = c.isSelf ? 1 : 0; + dataB[i * 4 + 2] = c.isTeammate ? 1 : 0; + dataB[i * 4 + 3] = 0; + } + gl.uniform4fv(this.uSpawnA, dataA); + gl.uniform4fv(this.uSpawnB, dataB); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + // tileTex owned by GPUResources + } +} diff --git a/src/client/render/gl/passes/structure-level-pass.ts b/src/client/render/gl/passes/structure-level-pass.ts new file mode 100644 index 0000000000..27e616458c --- /dev/null +++ b/src/client/render/gl/passes/structure-level-pass.ts @@ -0,0 +1,324 @@ +/** + * StructureLevelPass — MSDF-rendered level numbers above structures. + * + * Renders level digits for structures with level > 1 using the same MSDF + * atlas and glyph infrastructure as NamePass. One instanced draw call per frame. + * + * Only visible when zoom > dotsThreshold (matching structure icon visibility). + */ + +import type { RendererConfig, UnitState } from "../../types"; +import { + STRUCTURE_TYPES, + UT_CITY, + UT_DEFENSE_POST, + UT_FACTORY, + UT_MISSILE_SILO, + UT_PORT, + UT_SAM_LAUNCHER, +} from "../../types"; +import { DynamicInstanceBuffer } from "../dynamic-buffer"; +import type { RenderSettings } from "../render-settings"; +import { createProgram } from "../utils/gl-utils"; +import type { GlyphTables } from "./name-pass/atlas-data"; +import { buildGlyphTables, parseAtlasData } from "./name-pass/atlas-data"; +import { buildGlyphMetricsTex } from "./name-pass/data-textures"; +import { layoutString } from "./name-pass/text-layout"; +import { CHAR_RANGE, MAX_CHARS } from "./name-pass/types"; + +import atlasUrl from "../assets/msdf-atlas.png?url"; +import fragSrc from "../shaders/structure-level/structure-level.frag.glsl?raw"; +import vertSrc from "../shaders/structure-level/structure-level.vert.glsl?raw"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** Atlas column order — must match StructurePass. */ +const STRUCTURE_ORDER = [ + UT_CITY, + UT_PORT, + UT_FACTORY, + UT_DEFENSE_POST, + UT_SAM_LAUNCHER, + UT_MISSILE_SILO, +] as const; + +/** Max characters per level label (handles up to "99"). */ +const MAX_LEVEL_CHARS = 4; +const FLOATS_PER_INSTANCE = 5; // worldX, worldY, cursorX, charCode, atlasIdx +const BYTES_PER_INSTANCE = FLOATS_PER_INSTANCE * 4; + +// --------------------------------------------------------------------------- +// StructureLevelPass +// --------------------------------------------------------------------------- + +export class StructureLevelPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + // Uniform locations + private uCamera: WebGLUniformLocation; + private uZoom: WebGLUniformLocation; + private uIconSize: WebGLUniformLocation; + private uDotsThreshold: WebGLUniformLocation; + private uScaleFactor: WebGLUniformLocation; + private uDistRange: WebGLUniformLocation; + private uOutlineWidth: WebGLUniformLocation; + private uLevelScale: WebGLUniformLocation; + private uHighlightMask: WebGLUniformLocation; + private uHighlightDimAlpha: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + private instanceCount = 0; + + private glyphMetricsTex: WebGLTexture; + private atlasTex: WebGLTexture | null = null; + private atlasReady = false; + + // CPU-side glyph tables for layoutString + private glyph: GlyphTables; + private kernTable: Int8Array; + private mapW: number; + + // Reusable buffers for layoutString + private charCodes = new Uint8Array(MAX_CHARS); + private cursors = new Float32Array(MAX_CHARS); + + private distanceRange: number; + private fontSize: number; + private atlasScaleH: number; + private base: number; + + /** unitType string → atlas column index (0–5). */ + private typeToAtlasCol = new Map(); + /** Build-button hover highlight bitmask (0 = off). */ + private highlightMask = 0; + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = header.mapWidth; + + // Build unitType string → atlas column mapping + for (let i = 0; i < header.unitTypes.length; i++) { + const col = STRUCTURE_ORDER.indexOf( + header.unitTypes[i] as (typeof STRUCTURE_ORDER)[number], + ); + if (col >= 0) this.typeToAtlasCol.set(header.unitTypes[i], col); + } + + // Parse atlas data (same source as NamePass) + const atlas = parseAtlasData(); + this.glyph = buildGlyphTables(atlas.chars); + this.kernTable = new Int8Array(CHAR_RANGE * CHAR_RANGE); // digits don't kern + this.distanceRange = atlas.distanceRange; + this.fontSize = atlas.fontSize; + this.atlasScaleH = atlas.scaleH; + this.base = atlas.base; + + // Compile shaders + this.program = createProgram(gl, vertSrc, fragSrc); + + // Texture unit bindings + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uAtlas"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uGlyphMetrics"), 1); + + // Static uniforms + gl.uniform1f( + gl.getUniformLocation(this.program, "uFontSize")!, + this.fontSize, + ); + gl.uniform1f( + gl.getUniformLocation(this.program, "uAtlasScaleH")!, + this.atlasScaleH, + ); + gl.uniform1f(gl.getUniformLocation(this.program, "uBase")!, this.base); + + // Dynamic uniform locations + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uZoom = gl.getUniformLocation(this.program, "uZoom")!; + this.uIconSize = gl.getUniformLocation(this.program, "uIconSize")!; + this.uDotsThreshold = gl.getUniformLocation( + this.program, + "uDotsThreshold", + )!; + this.uScaleFactor = gl.getUniformLocation(this.program, "uScaleFactor")!; + this.uDistRange = gl.getUniformLocation(this.program, "uDistRange")!; + this.uOutlineWidth = gl.getUniformLocation(this.program, "uOutlineWidth")!; + this.uLevelScale = gl.getUniformLocation(this.program, "uLevelScale")!; + this.uHighlightMask = gl.getUniformLocation( + this.program, + "uHighlightMask", + )!; + this.uHighlightDimAlpha = gl.getUniformLocation( + this.program, + "uHighlightDimAlpha", + )!; + + // Glyph metrics data texture + this.glyphMetricsTex = buildGlyphMetricsTex(gl, atlas); + + // Start async MSDF atlas load + this.loadAtlas(); + + // Instance buffer + const glBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer( + gl, + glBuf, + 4096, + FLOATS_PER_INSTANCE, + ); + + // VAO + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + // Attribute 0: unit quad [0,1]² + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Attribute 1: per-instance vec4 (worldX, worldY, cursorX, charCode) + gl.bindBuffer(gl.ARRAY_BUFFER, glBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribDivisor(1, 1); + + // Attribute 2: per-instance float (atlasIdx) + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 1, gl.FLOAT, false, BYTES_PER_INSTANCE, 16); + gl.vertexAttribDivisor(2, 1); + + gl.bindVertexArray(null); + } + + private loadAtlas(): void { + const img = new Image(); + img.onload = () => { + const gl = this.gl; + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + this.atlasTex = tex; + this.atlasReady = true; + }; + img.src = atlasUrl; + } + + updateStructures(units: Map): void { + let count = 0; + + for (const unit of units.values()) { + if (!STRUCTURE_TYPES.has(unit.unitType)) continue; + if (unit.level <= 1) continue; + + const levelStr = unit.level.toString(); + layoutString( + levelStr, + this.glyph, + this.kernTable, + this.charCodes, + this.cursors, + ); + + const x = unit.pos % this.mapW; + const y = (unit.pos - x) / this.mapW; + const len = Math.min(levelStr.length, MAX_LEVEL_CHARS); + const atlasIdx = this.typeToAtlasCol.get(unit.unitType) ?? 0; + + for (let i = 0; i < len; i++) { + this.instanceBuf.ensureCapacity(count + 1); + + const off = count * FLOATS_PER_INSTANCE; + const data = this.instanceBuf.float32; + data[off + 0] = x; + data[off + 1] = y; + data[off + 2] = this.cursors[i]; + data[off + 3] = this.charCodes[i]; + data[off + 4] = atlasIdx; + count++; + } + } + + this.instanceCount = count; + + if (count > 0) { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + count * FLOATS_PER_INSTANCE, + ); + } + } + + draw(cameraMatrix: Float32Array, zoom: number): void { + if (!this.atlasReady || this.instanceCount === 0) return; + + const gl = this.gl; + const ss = this.settings.structure; + const sl = this.settings.structureLevel; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uZoom, zoom); + gl.uniform1f(this.uIconSize, ss.iconSize); + gl.uniform1f(this.uDotsThreshold, ss.dotsZoomThreshold); + gl.uniform1f(this.uScaleFactor, ss.iconScaleFactorZoomedOut); + gl.uniform1f(this.uDistRange, this.distanceRange); + gl.uniform1f(this.uOutlineWidth, sl.outlineWidth); + gl.uniform1f(this.uLevelScale, sl.scale); + gl.uniform1i(this.uHighlightMask, this.highlightMask); + gl.uniform1f(this.uHighlightDimAlpha, ss.highlightDimAlpha); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex!); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.glyphMetricsTex); + + gl.bindVertexArray(this.vao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); + } + + /** Highlight structures of the given types (null/empty = off). Dims all other types. */ + setHighlightTypes(unitTypes: string[] | null): void { + let mask = 0; + if (unitTypes) { + for (const t of unitTypes) { + const col = this.typeToAtlasCol.get(t); + if (col !== undefined) mask |= 1 << col; + } + } + this.highlightMask = mask; + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + gl.deleteVertexArray(this.vao); + gl.deleteTexture(this.glyphMetricsTex); + if (this.atlasTex) gl.deleteTexture(this.atlasTex); + } +} diff --git a/src/client/render/gl/passes/structure-pass.ts b/src/client/render/gl/passes/structure-pass.ts new file mode 100644 index 0000000000..d0f89c258f --- /dev/null +++ b/src/client/render/gl/passes/structure-pass.ts @@ -0,0 +1,435 @@ +/** + * StructurePass — GPU-rendered structures with icon sprites. + * + * Renders a filled circle in player color with a white icon overlay, + * sampled from a pre-built 6-column sprite atlas (generate-sprite-atlases.mjs). + * + * Two LODs based on zoom: + * - zoom > 0.5: full icon with circle background + * - zoom <= 0.5: smaller dots (no icon detail) + * + * One instanced draw call per frame. + * + * Data flow: + * FrameSnapshot.units → filter structures → instance VBO → GPU + */ + +import type { GhostPreviewData, RendererConfig, UnitState } from "../../types"; +import { + UT_CITY, + UT_DEFENSE_POST, + UT_FACTORY, + UT_MISSILE_SILO, + UT_PORT, + UT_SAM_LAUNCHER, +} from "../../types"; +import { DynamicInstanceBuffer } from "../dynamic-buffer"; +import type { RenderSettings } from "../render-settings"; +import { getPaletteSize } from "../utils/color-utils"; +import { createProgram, shaderSrc } from "../utils/gl-utils"; + +import structureFragSrc from "../shaders/structure/structure.frag.glsl?raw"; +import structureVertSrc from "../shaders/structure/structure.vert.glsl?raw"; + +// Pre-built icon atlas (generated by scripts/generate-sprite-atlases.mjs) +import iconAtlasUrl from "../assets/icon-atlas.png?url"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** + * Structure types in atlas column order. + * Index = atlas column index. + */ +const STRUCTURE_ORDER = [ + UT_CITY, + UT_PORT, + UT_FACTORY, + UT_DEFENSE_POST, + UT_SAM_LAUNCHER, + UT_MISSILE_SILO, +] as const; + +const ATLAS_COLS = STRUCTURE_ORDER.length; + +// --------------------------------------------------------------------------- +// Instance data layout +// --------------------------------------------------------------------------- + +// Per-instance: x, y, ownerID, underConstruction, atlasIdx, markedForDeletion +const FLOATS_PER_INSTANCE = 6; +const BYTES_PER_INSTANCE = FLOATS_PER_INSTANCE * 4; + +// --------------------------------------------------------------------------- +// StructurePass +// --------------------------------------------------------------------------- + +export class StructurePass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + + private uCamera: WebGLUniformLocation; + private uZoom: WebGLUniformLocation; + private uIconSize: WebGLUniformLocation; + private uDotsThreshold: WebGLUniformLocation; + private uScaleFactor: WebGLUniformLocation; + private uShapeScales: WebGLUniformLocation; + private uIconFills: WebGLUniformLocation; + private uGhostAlpha: WebGLUniformLocation; + private uOutlineColor: WebGLUniformLocation; + private uAltView: WebGLUniformLocation; + private uHighlightMask: WebGLUniformLocation; + private uHighlightOutlineW: WebGLUniformLocation; + private uHighlightDimAlpha: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private instanceBuf: DynamicInstanceBuffer; + private ghostInstanceBuf: WebGLBuffer; + + private paletteTex: WebGLTexture; + private atlasTex: WebGLTexture; + private affiliationTex: WebGLTexture | null = null; + private altView = false; + + private instanceCount = 0; + + /** unitType string → atlas column index (0–5) */ + private typeToAtlasCol = new Map(); + private mapW: number; + + /** Build-button hover highlight: bitmask of atlas columns (0 = off). */ + private highlightMask = 0; + + /** Ghost preview state (null = no ghost). */ + private ghost: GhostPreviewData | null = null; + /** Scratch buffer for the single ghost instance (avoids allocation). */ + private ghostBuf = new Float32Array(FLOATS_PER_INSTANCE); + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + paletteTex: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = header.mapWidth; + this.paletteTex = paletteTex; + + // Build unitType string → atlas column mapping + for (let i = 0; i < header.unitTypes.length; i++) { + const col = STRUCTURE_ORDER.indexOf( + header.unitTypes[i] as (typeof STRUCTURE_ORDER)[number], + ); + if (col >= 0) { + this.typeToAtlasCol.set(header.unitTypes[i], col); + } + } + + // Compile shaders + this.program = createProgram( + gl, + shaderSrc(structureVertSrc, { ATLAS_COLS }), + shaderSrc(structureFragSrc, { + PALETTE_SIZE: getPaletteSize(), + ATLAS_COLS, + }), + ); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uZoom = gl.getUniformLocation(this.program, "uZoom")!; + this.uIconSize = gl.getUniformLocation(this.program, "uIconSize")!; + this.uDotsThreshold = gl.getUniformLocation( + this.program, + "uDotsThreshold", + )!; + this.uScaleFactor = gl.getUniformLocation(this.program, "uScaleFactor")!; + this.uShapeScales = gl.getUniformLocation(this.program, "uShapeScales")!; + this.uIconFills = gl.getUniformLocation(this.program, "uIconFills")!; + this.uGhostAlpha = gl.getUniformLocation(this.program, "uGhostAlpha")!; + this.uOutlineColor = gl.getUniformLocation(this.program, "uOutlineColor")!; + this.uAltView = gl.getUniformLocation(this.program, "uAltView")!; + this.uHighlightMask = gl.getUniformLocation( + this.program, + "uHighlightMask", + )!; + this.uHighlightOutlineW = gl.getUniformLocation( + this.program, + "uHighlightOutlineW", + )!; + this.uHighlightDimAlpha = gl.getUniformLocation( + this.program, + "uHighlightDimAlpha", + )!; + + // Texture unit bindings + ghost defaults + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uPalette"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uAtlas"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uAffiliation"), 2); + gl.uniform1f(this.uGhostAlpha, 1.0); + gl.uniform3f(this.uOutlineColor, 0, 0, 0); + gl.uniform1i(this.uHighlightMask, 0); + + // Create placeholder atlas texture (1×1 white pixel) + // Replaced asynchronously once SVGs load + this.atlasTex = gl.createTexture()!; + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + 1, + 1, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + new Uint8Array([255, 255, 255, 255]), + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + // Start async atlas build + this.loadAtlas(); + + // --- Instance buffers --- + const instanceGlBuf = gl.createBuffer()!; + this.instanceBuf = new DynamicInstanceBuffer( + gl, + instanceGlBuf, + 2048, + FLOATS_PER_INSTANCE, + ); + + // Separate tiny buffer for ghost (avoids corrupting real instance data) + this.ghostInstanceBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, this.ghostInstanceBuf); + gl.bufferData(gl.ARRAY_BUFFER, BYTES_PER_INSTANCE, gl.DYNAMIC_DRAW); + + // --- VAO --- + this.vao = gl.createVertexArray()!; + gl.bindVertexArray(this.vao); + + // Attribute 0: unit quad [0,0]→[1,1] + const quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Attribute 1: per-instance vec4 (x, y, ownerID, underConstruction) + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribDivisor(1, 1); + + // Attribute 2: per-instance vec2 (atlasIdx, markedForDeletion) + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 2, gl.FLOAT, false, BYTES_PER_INSTANCE, 16); + gl.vertexAttribDivisor(2, 1); + + gl.bindVertexArray(null); + } + + private async loadAtlas(): Promise { + const img = new Image(); + img.src = iconAtlasUrl; + await img.decode(); + const gl = this.gl; + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + gl.generateMipmap(gl.TEXTURE_2D); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + gl.LINEAR_MIPMAP_LINEAR, + ); + } + + updateStructures(units: Map): void { + let count = 0; + + for (const unit of units.values()) { + const atlasIdx = this.typeToAtlasCol.get(unit.unitType); + if (atlasIdx === undefined) continue; + + this.instanceBuf.ensureCapacity(count + 1); + + const off = count * FLOATS_PER_INSTANCE; + const x = unit.pos % this.mapW; + const y = (unit.pos - x) / this.mapW; + + this.instanceBuf.float32[off + 0] = x; + this.instanceBuf.float32[off + 1] = y; + this.instanceBuf.float32[off + 2] = unit.ownerID; + this.instanceBuf.float32[off + 3] = unit.underConstruction ? 1 : 0; + this.instanceBuf.float32[off + 4] = atlasIdx; + this.instanceBuf.float32[off + 5] = + unit.markedForDeletion !== false ? 1 : 0; + + count++; + } + + this.instanceCount = count; + + if (count > 0) { + const gl = this.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.instanceBuf.float32, + 0, + count * FLOATS_PER_INSTANCE, + ); + } + } + + updateGhostPreview(data: GhostPreviewData | null): void { + this.ghost = data; + } + + setAltView(active: boolean): void { + this.altView = active; + } + + /** Highlight structures of the given types (null/empty = off). Dims all other types. */ + setHighlightTypes(unitTypes: string[] | null): void { + let mask = 0; + if (unitTypes) { + for (const t of unitTypes) { + const col = this.typeToAtlasCol.get(t); + if (col !== undefined) mask |= 1 << col; + } + } + this.highlightMask = mask; + } + setAffiliationTex(tex: WebGLTexture): void { + this.affiliationTex = tex; + } + + draw(cameraMatrix: Float32Array, zoom: number): void { + const hasGhost = + this.ghost !== null && this.typeToAtlasCol.has(this.ghost.ghostType); + if (this.instanceCount === 0 && !hasGhost) return; + + const gl = this.gl; + gl.useProgram(this.program); + + const ss = this.settings.structure; + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uZoom, zoom); + gl.uniform1f(this.uIconSize, ss.iconSize); + gl.uniform1f(this.uDotsThreshold, ss.dotsZoomThreshold); + gl.uniform1f(this.uScaleFactor, ss.iconScaleFactorZoomedOut); + + // Build per-structure uniform arrays from settings, ordered by atlas column + const scales = new Float32Array(ATLAS_COLS); + const fills = new Float32Array(ATLAS_COLS); + for (let i = 0; i < STRUCTURE_ORDER.length; i++) { + const cfg = ss.shapes[STRUCTURE_ORDER[i]]; + scales[i] = cfg?.scale ?? 1.0; + fills[i] = cfg?.iconFill ?? 0.6; + } + gl.uniform1fv(this.uShapeScales, scales); + gl.uniform1fv(this.uIconFills, fills); + + gl.uniform1i( + this.uAltView, + this.altView && this.settings.altView.recolorStructures ? 1 : 0, + ); + gl.uniform1i(this.uHighlightMask, this.highlightMask); + gl.uniform1f(this.uHighlightOutlineW, ss.highlightOutlineWidth); + gl.uniform1f(this.uHighlightDimAlpha, ss.highlightDimAlpha); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + + if (this.affiliationTex) { + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.affiliationTex); + } + + gl.bindVertexArray(this.vao); + + // --- Real structures --- + if (this.instanceCount > 0) { + gl.uniform1f(this.uGhostAlpha, 1.0); + gl.uniform3f(this.uOutlineColor, 0, 0, 0); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.instanceCount); + } + + // --- Ghost structure (1 translucent instance with outline) --- + if (hasGhost) { + const g = this.ghost!; + const atlasIdx = this.typeToAtlasCol.get(g.ghostType)!; + + // Temporarily rebind instance attrs to ghost buffer + gl.bindBuffer(gl.ARRAY_BUFFER, this.ghostInstanceBuf); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribPointer(2, 2, gl.FLOAT, false, BYTES_PER_INSTANCE, 16); + + // -- Green highlight on existing structure being upgraded -- + if (g.canUpgrade && g.upgradeTargetTile !== null) { + const tx = g.upgradeTargetTile % this.mapW; + const ty = (g.upgradeTargetTile - tx) / this.mapW; + this.ghostBuf[0] = tx; + this.ghostBuf[1] = ty; + this.ghostBuf[2] = g.ownerID; + this.ghostBuf[3] = 0; + this.ghostBuf[4] = atlasIdx; + this.ghostBuf[5] = 0; + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.ghostBuf); + + gl.uniform1f(this.uGhostAlpha, 0.6); + gl.uniform3f(this.uOutlineColor, 0.0, 0.8, 0.0); // green highlight + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 1); + } + + // -- Ghost icon at cursor -- + this.ghostBuf[0] = g.tileX; + this.ghostBuf[1] = g.tileY; + this.ghostBuf[2] = g.ownerID; + this.ghostBuf[3] = 0; + this.ghostBuf[4] = atlasIdx; + this.ghostBuf[5] = 0; + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.ghostBuf); + + gl.uniform1f(this.uGhostAlpha, 0.5); + if (g.canUpgrade) { + gl.uniform3f(this.uOutlineColor, 0.0, 0.8, 0.0); // green tint — upgrade + } else if (g.canBuild) { + gl.uniform3f(this.uOutlineColor, 0, 0, 0); // no tint — valid build + } else { + gl.uniform3f(this.uOutlineColor, 0.8, 0.2, 0.2); // red tint — can't build + } + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 1); + + // Restore instance attrs to main buffer + gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuf.buffer); + gl.vertexAttribPointer(1, 4, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribPointer(2, 2, gl.FLOAT, false, BYTES_PER_INSTANCE, 16); + } + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.instanceBuf.dispose(); + if (this.ghostInstanceBuf) gl.deleteBuffer(this.ghostInstanceBuf); + gl.deleteVertexArray(this.vao); + gl.deleteTexture(this.atlasTex); + } +} diff --git a/src/client/render/gl/passes/terrain-pass.ts b/src/client/render/gl/passes/terrain-pass.ts new file mode 100644 index 0000000000..812f43c138 --- /dev/null +++ b/src/client/render/gl/passes/terrain-pass.ts @@ -0,0 +1,77 @@ +/** + * TerrainPass — renders the static terrain map as a textured quad. + * + * The terrain never changes during a replay, so this texture is uploaded + * exactly once and blitted every frame as the opaque background layer. + * + * Vertex shader transforms the map quad by the camera mat3. + * Fragment shader samples the RGBA8 terrain texture with nearest-neighbour + * filtering so each terrain cell stays pixel-crisp at every zoom level. + */ + +import terrainFragSrc from "../shaders/terrain/terrain.frag.glsl?raw"; +import terrainVertSrc from "../shaders/terrain/terrain.vert.glsl?raw"; +import { + createMapQuad, + createProgram, + createTexture2D, + shaderSrc, +} from "../utils/gl-utils"; + +// --------------------------------------------------------------------------- +// TerrainPass +// --------------------------------------------------------------------------- + +export class TerrainPass { + private program: WebGLProgram; + private tex: WebGLTexture; + private vao: WebGLVertexArrayObject; + private uCamera: WebGLUniformLocation; + + constructor( + private gl: WebGL2RenderingContext, + terrainRGBA: Uint8Array, + mapW: number, + mapH: number, + ) { + this.program = createProgram( + gl, + shaderSrc(terrainVertSrc, { MAP_W: mapW, MAP_H: mapH }), + terrainFragSrc, + ); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + + // Static RGBA8 terrain texture — uploaded once, never updated. + this.tex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.RGBA8, + format: gl.RGBA, + type: gl.UNSIGNED_BYTE, + data: terrainRGBA, + filter: gl.NEAREST, // pixel-crisp at all zoom levels + }); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + /** Render the terrain. Call with depth test disabled, no blending. */ + draw(cameraMatrix: Float32Array): void { + const gl = this.gl; + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.tex); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteTexture(this.tex); + // VAO + buffer leak is acceptable on dispose (context is being destroyed) + } +} diff --git a/src/client/render/gl/passes/territory-pass.ts b/src/client/render/gl/passes/territory-pass.ts new file mode 100644 index 0000000000..c2e7485758 --- /dev/null +++ b/src/client/render/gl/passes/territory-pass.ts @@ -0,0 +1,353 @@ +/** + * TerritoryPass — territory fill + fallout charcoal ground. + * + * Draws only what should be darkened by the night cycle: + * - Owned territory (player color fill) + * - Unowned fallout (charcoal ground) + * + * No borders, embers, trails, or defense checkerboard — those are + * handled by BorderStampPass and TrailPass at full brightness. + * + * Also owns the CPU-side tile and trail state, flushing to shared + * GPU textures on draw. + */ + +import type { TilePair } from "../../types"; +import type { RenderSettings } from "../render-settings"; +import { getPaletteSize } from "../utils/color-utils"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; +import { OWNER_MASK, TILE_DEFINES } from "../utils/tile-codec"; + +import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; +import territoryFragSrc from "../shaders/map-overlay/territory.frag.glsl?raw"; + +export class TerritoryPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + + private program: WebGLProgram; + private uCamera: WebGLUniformLocation; + private uMapSize: WebGLUniformLocation; + private uAltView: WebGLUniformLocation; + private uCharcoalBase: WebGLUniformLocation; + private uCharcoalVariation: WebGLUniformLocation; + private uCharcoalAlpha: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private tileTex: WebGLTexture; + private trailTex: WebGLTexture; + private paletteTex: WebGLTexture; + + private altView = false; + + /** CPU-side tile state (deltas written here, flushed to GPU before draw). */ + private cpuTileState: Uint16Array; + private tilesDirty = false; + + /** CPU-side trail state (R8UI, 0=none, 1–255=ownerID). */ + private cpuTrailState: Uint8Array; + private trailsDirty = false; + + /** Live-game references — bypasses memcpy. Null for replay path. */ + private liveTileRef: Uint16Array | null = null; + private liveTrailRef: Uint8Array | null = null; + + /** Dirty row range for partial tile upload. Infinity/-1 = full upload. */ + private dirtyRowMin = Infinity; + private dirtyRowMax = -1; + + /** Dirty row range for partial trail upload. Infinity/-1 = full upload. */ + private trailDirtyRowMin = Infinity; + private trailDirtyRowMax = -1; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + trailTex: WebGLTexture, + paletteTex: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.trailTex = trailTex; + this.paletteTex = paletteTex; + this.cpuTileState = new Uint16Array(mapW * mapH); + this.cpuTrailState = new Uint8Array(mapW * mapH); + + this.program = createProgram( + gl, + overlayVertSrc, + shaderSrc(territoryFragSrc, { + PALETTE_SIZE: getPaletteSize(), + ...TILE_DEFINES, + }), + ); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uAltView = gl.getUniformLocation(this.program, "uAltView")!; + this.uCharcoalBase = gl.getUniformLocation(this.program, "uCharcoalBase")!; + this.uCharcoalVariation = gl.getUniformLocation( + this.program, + "uCharcoalVariation", + )!; + this.uCharcoalAlpha = gl.getUniformLocation( + this.program, + "uCharcoalAlpha", + )!; + + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uPalette"), 1); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + // --------------------------------------------------------------------------- + // Tile data upload + // --------------------------------------------------------------------------- + + /** Full tile state upload (on seek). */ + uploadFullTileState(tileState: Uint16Array): void { + this.liveTileRef = null; + this.cpuTileState.set(tileState); + this.tilesDirty = true; + } + + /** Live-game path: reference the game's own arrays directly. */ + setLiveRefs(tileState: Uint16Array, trailState: Uint8Array): void { + this.liveTileRef = tileState; + this.liveTrailRef = trailState; + this.tilesDirty = true; + this.trailsDirty = true; + } + + /** Apply tile deltas (during playback). */ + uploadDeltaTiles(changedTiles: TilePair[]): void { + const ts = this.cpuTileState; + for (let i = 0; i < changedTiles.length; i++) { + const tp = changedTiles[i]; + ts[tp.ref] = tp.state; + } + this.tilesDirty = true; + } + + /** Live delta: update live ref + compute dirty row range from deltas. */ + applyLiveDelta(tileState: Uint16Array, changedTiles: TilePair[]): void { + this.liveTileRef = tileState; + let minRow = Infinity, + maxRow = -1; + for (let i = 0; i < changedTiles.length; i++) { + const row = (changedTiles[i].ref / this.mapW) | 0; + if (row < minRow) minRow = row; + if (row > maxRow) maxRow = row; + } + if (maxRow >= 0) { + this.dirtyRowMin = Math.min(this.dirtyRowMin, minRow); + this.dirtyRowMax = Math.max(this.dirtyRowMax, maxRow); + } + this.tilesDirty = true; + } + + /** Live trail delta: update live ref + accept dirty row range from TrailManager. */ + applyLiveTrailDelta( + trailState: Uint8Array, + dirtyRowMin: number, + dirtyRowMax: number, + ): void { + this.liveTrailRef = trailState; + if (dirtyRowMax >= 0) { + this.trailDirtyRowMin = Math.min(this.trailDirtyRowMin, dirtyRowMin); + this.trailDirtyRowMax = Math.max(this.trailDirtyRowMax, dirtyRowMax); + } + this.trailsDirty = true; + } + + /** Full trail state upload (on seek). */ + uploadFullTrailState(trailState: Uint8Array): void { + this.liveTrailRef = null; + this.cpuTrailState.set(trailState); + this.trailsDirty = true; + } + + /** Set a single trail tile (during playback advance). */ + setTrailTile(ref: number, ownerID: number): void { + this.cpuTrailState[ref] = ownerID; + this.trailsDirty = true; + } + + /** Clear all trails (on seek before rebuilding). */ + clearTrails(): void { + this.cpuTrailState.fill(0); + this.trailsDirty = true; + } + + // --------------------------------------------------------------------------- + // Queries + // --------------------------------------------------------------------------- + + /** Get ownerID at a tile reference. Returns 0 for unowned. */ + getOwnerAt(tileRef: number): number { + const ts = this.liveTileRef ?? this.cpuTileState; + if (tileRef < 0 || tileRef >= ts.length) return 0; + return ts[tileRef] & OWNER_MASK; + } + + /** AABB of all tiles owned by ownerID. */ + getBBoxForOwner( + ownerID: number, + ): { minX: number; minY: number; maxX: number; maxY: number } | null { + let minX = Infinity, + minY = Infinity, + maxX = -Infinity, + maxY = -Infinity; + const w = this.mapW; + const ts = this.liveTileRef ?? this.cpuTileState; + for (let i = 0; i < ts.length; i++) { + if ((ts[i] & OWNER_MASK) === ownerID) { + const x = i % w; + const y = (i - x) / w; + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + } + } + return minX === Infinity ? null : { minX, minY, maxX, maxY }; + } + + // --------------------------------------------------------------------------- + // GPU flush + draw + // --------------------------------------------------------------------------- + + /** Flush tile texture to GPU early (before heat update reads it). Returns true if data was uploaded. */ + flushTileTexture(): boolean { + if (!this.tilesDirty) return false; + const gl = this.gl; + const src = this.liveTileRef ?? this.cpuTileState; + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + + if (this.dirtyRowMax >= 0) { + // Partial upload — only dirty rows + const minRow = this.dirtyRowMin; + const rowCount = this.dirtyRowMax - minRow + 1; + const offset = minRow * this.mapW; + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + minRow, + this.mapW, + rowCount, + gl.RED_INTEGER, + gl.UNSIGNED_SHORT, + src.subarray(offset, offset + rowCount * this.mapW), + ); + } else { + // Full upload (first tick, seek, replay full frame, etc.) + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + this.mapW, + this.mapH, + gl.RED_INTEGER, + gl.UNSIGNED_SHORT, + src, + ); + } + + this.dirtyRowMin = Infinity; + this.dirtyRowMax = -1; + this.tilesDirty = false; + return true; + } + + /** Flush trail texture to GPU (called before TrailPass draws). */ + flushTrailTexture(): void { + if (!this.trailsDirty) return; + const gl = this.gl; + const src = this.liveTrailRef ?? this.cpuTrailState; + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.trailTex); + + if (this.trailDirtyRowMax >= 0) { + // Partial upload — only dirty rows + const minRow = this.trailDirtyRowMin; + const rowCount = this.trailDirtyRowMax - minRow + 1; + const offset = minRow * this.mapW; + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + minRow, + this.mapW, + rowCount, + gl.RED_INTEGER, + gl.UNSIGNED_BYTE, + src.subarray(offset, offset + rowCount * this.mapW), + ); + } else { + // Full upload (first tick, seek, replay, etc.) + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + this.mapW, + this.mapH, + gl.RED_INTEGER, + gl.UNSIGNED_BYTE, + src, + ); + } + + this.trailDirtyRowMin = Infinity; + this.trailDirtyRowMax = -1; + this.trailsDirty = false; + } + + setAltView(active: boolean): void { + this.altView = active; + } + + /** Draw territory fill + fallout charcoal. Blending must be enabled by caller. */ + draw(cameraMatrix: Float32Array): void { + this.flushTileTexture(); + this.flushTrailTexture(); + + const gl = this.gl; + const mo = this.settings.mapOverlay; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1i(this.uAltView, this.altView ? 1 : 0); + gl.uniform1f(this.uCharcoalBase, mo.charcoalBase); + gl.uniform1f(this.uCharcoalVariation, mo.charcoalVariation); + gl.uniform1f(this.uCharcoalAlpha, mo.charcoalAlpha); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + // tileTex, trailTex, paletteTex owned by GPUResources / renderer + } +} diff --git a/src/client/render/gl/passes/trail-pass.ts b/src/client/render/gl/passes/trail-pass.ts new file mode 100644 index 0000000000..5cad9b7376 --- /dev/null +++ b/src/client/render/gl/passes/trail-pass.ts @@ -0,0 +1,106 @@ +/** + * TrailPass — boat trail lines. + * + * Simple dedicated pass: for each tile with a non-zero trail owner, + * output the owner's territory color at configurable alpha. + * Always draws at full brightness (after night composite). + */ + +import type { RenderSettings } from "../render-settings"; +import { getPaletteSize } from "../utils/color-utils"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; +import { TILE_DEFINES } from "../utils/tile-codec"; + +import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; +import trailFragSrc from "../shaders/map-overlay/trail.frag.glsl?raw"; + +export class TrailPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + + private program: WebGLProgram; + private uCamera: WebGLUniformLocation; + private uMapSize: WebGLUniformLocation; + private uTrailAlpha: WebGLUniformLocation; + private uAltView: WebGLUniformLocation; + + private vao: WebGLVertexArrayObject; + private trailTex: WebGLTexture; + private paletteTex: WebGLTexture; + private affiliationTex: WebGLTexture | null = null; + private altView = false; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + trailTex: WebGLTexture, + paletteTex: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.trailTex = trailTex; + this.paletteTex = paletteTex; + + this.program = createProgram( + gl, + overlayVertSrc, + shaderSrc(trailFragSrc, { + PALETTE_SIZE: getPaletteSize(), + ...TILE_DEFINES, + }), + ); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uMapSize = gl.getUniformLocation(this.program, "uMapSize")!; + this.uTrailAlpha = gl.getUniformLocation(this.program, "uTrailAlpha")!; + this.uAltView = gl.getUniformLocation(this.program, "uAltView")!; + + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uTrailTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uPalette"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uAffiliation"), 2); + + this.vao = createMapQuad(gl, mapW, mapH); + } + + setAltView(active: boolean): void { + this.altView = active; + } + setAffiliationTex(tex: WebGLTexture): void { + this.affiliationTex = tex; + } + + /** Draw trail overlay. Blending must be enabled by caller. */ + draw(cameraMatrix: Float32Array): void { + const gl = this.gl; + + gl.useProgram(this.program); + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform2f(this.uMapSize, this.mapW, this.mapH); + gl.uniform1f(this.uTrailAlpha, this.settings.mapOverlay.trailAlpha); + gl.uniform1i(this.uAltView, this.altView ? 1 : 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.trailTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + if (this.affiliationTex) { + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.affiliationTex); + } + + gl.bindVertexArray(this.vao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + gl.deleteVertexArray(this.vao); + } +} diff --git a/src/client/render/gl/passes/unit-pass.ts b/src/client/render/gl/passes/unit-pass.ts new file mode 100644 index 0000000000..08be21d472 --- /dev/null +++ b/src/client/render/gl/passes/unit-pass.ts @@ -0,0 +1,513 @@ +/** + * UnitPass — GPU-rendered mobile unit sprites. + * + * Renders all mobile (non-structure) units: boats, nukes, shells, SAM + * missiles, and MIRV warheads. All unit types are rotationally symmetric + * — no rotation needed. Sprites are tiny grayscale PNGs colorized on the + * GPU using the standard 3-band gray replacement (180/130/70). Shell and + * MIRV Warhead use programmatic 3×3 white squares (colorized to border + * color). + * + * Two instanced draw calls per frame — ground units and missiles are + * split into separate buffers for correct layer ordering: + * Ground/sea (boats, trains) → rendered below structures + * Missiles (nukes, shells, SAM, MIRV warheads) → rendered above structures + * + * Atlas layout (12 columns × 13px cells, pre-built by generate-sprite-atlases.mjs): + * Col 0: Transport (5×5) + * Col 1: Trade Ship (5×5) + * Col 2: Warship (11×11) + * Col 3: Atom Bomb (7×7) + * Col 4: Hydrogen Bomb (9×9) + * Col 5: MIRV (13×13, grayscale colorized) + * Col 6: SAM Missile (3×3) + * Col 7: Shell (3×3 white square) + * Col 8: MIRV Warhead (3×3 white square) + * Col 9: Train Engine (5×5) + * Col 10: Train Carriage (5×5) + * Col 11: Train Carriage Loaded (5×5) + * + * Data flow: + * FrameSnapshot.units → filter by typeToAtlasIdx → instance VBO → GPU + * Shells emit 2 instances (pos + lastPos) to match live game's 2-pixel trail. + */ + +import type { RendererConfig, UnitState } from "../../types"; +import { + TrainType, + UT_ATOM_BOMB, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_MIRV_WARHEAD, + UT_SAM_MISSILE, + UT_SHELL, + UT_TRADE_SHIP, + UT_TRAIN, + UT_TRANSPORT, + UT_WARSHIP, +} from "../../types"; +import { DynamicInstanceBuffer } from "../dynamic-buffer"; +import type { RenderSettings } from "../render-settings"; +import unitFragSrc from "../shaders/unit/unit.frag.glsl?raw"; +import unitVertSrc from "../shaders/unit/unit.vert.glsl?raw"; +import { getPaletteSize } from "../utils/color-utils"; +import { createProgram, shaderSrc } from "../utils/gl-utils"; + +// Pre-built sprite atlas (generated by scripts/generate-sprite-atlases.mjs) +import unitAtlasUrl from "../assets/unit-atlas.png?url"; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +/** Unit types in atlas column order. Index = atlas column. + * TrainEngine/TrainCarriage/TrainCarriageLoaded are synthetic names — + * they don't match header.unitTypes directly. Train resolution is + * handled specially in updateUnits() via trainType + loaded fields. + */ +const UNIT_ORDER = [ + UT_TRANSPORT, + UT_TRADE_SHIP, + UT_WARSHIP, + UT_ATOM_BOMB, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_SAM_MISSILE, + UT_SHELL, + UT_MIRV_WARHEAD, + "TrainEngine", + "TrainCarriage", + "TrainCarriageLoaded", +] as const; + +const ATLAS_COLS = UNIT_ORDER.length; + +// --------------------------------------------------------------------------- +// Instance data layout +// --------------------------------------------------------------------------- + +/** + * Per-instance data (16 bytes): + * float x, y, ownerID — 12 bytes (3 floats) + * uint8 atlasIdx — 1 byte (atlas column 0–11) + * uint8 flags — 1 byte (0 = normal, 1 = flicker, 2 = angry) + * 2 bytes padding — aligns to 4-byte boundary + */ +const FLOATS_PER_INSTANCE = 4; +const BYTES_PER_INSTANCE = FLOATS_PER_INSTANCE * 4; + +/** Flag values — passed as uint8, received as float in shader via normalized attribute */ +const FLAG_NORMAL = 0; +const FLAG_FLICKER = 1; +const FLAG_ANGRY = 2; +const FLAG_TRADE_FRIENDLY = 3; + +/** Atlas column indices for train sub-types (resolved from trainType + loaded) */ +const TRAIN_ENGINE_COL = UNIT_ORDER.indexOf("TrainEngine"); +const TRAIN_CARRIAGE_COL = UNIT_ORDER.indexOf("TrainCarriage"); +const TRAIN_CARRIAGE_LOADED_COL = UNIT_ORDER.indexOf("TrainCarriageLoaded"); + +/** Nuke + warhead types — rendered with flickering hot colors */ +const FLICKER_TYPES: ReadonlySet = new Set([ + UT_ATOM_BOMB, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_MIRV_WARHEAD, + UT_SAM_MISSILE, + UT_SHELL, +]); + +/** Missile/projectile types — rendered on top of structures in the layer order. + * Ground/sea units (boats, trains) render below structures. */ +const MISSILE_TYPES: ReadonlySet = new Set([ + UT_ATOM_BOMB, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_SAM_MISSILE, + UT_SHELL, + UT_MIRV_WARHEAD, +]); + +// --------------------------------------------------------------------------- +// Helper: create a VAO for instanced unit rendering +// --------------------------------------------------------------------------- + +function createUnitVao( + gl: WebGL2RenderingContext, + quadBuf: WebGLBuffer, + instanceBuf: WebGLBuffer, +): WebGLVertexArrayObject { + const vao = gl.createVertexArray()!; + gl.bindVertexArray(vao); + + // Attribute 0: unit quad [0,0]->[1,1] + gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Attribute 1: per-instance vec3 (x, y, ownerID) — 3 floats at offset 0 + gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuf); + gl.enableVertexAttribArray(1); + gl.vertexAttribPointer(1, 3, gl.FLOAT, false, BYTES_PER_INSTANCE, 0); + gl.vertexAttribDivisor(1, 1); + + // Attribute 2: per-instance (atlasIdx, flags) — 2 uint8s at offset 12, converted to float + gl.enableVertexAttribArray(2); + gl.vertexAttribPointer(2, 2, gl.UNSIGNED_BYTE, false, BYTES_PER_INSTANCE, 12); + gl.vertexAttribDivisor(2, 1); + + gl.bindVertexArray(null); + return vao; +} + +// --------------------------------------------------------------------------- +// UnitPass +// --------------------------------------------------------------------------- + +export class UnitPass { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private program: WebGLProgram; + + private uCamera: WebGLUniformLocation; + private uTick: WebGLUniformLocation; + private uUnitSize: WebGLUniformLocation; + private uFlickerSpeed: WebGLUniformLocation; + private uAngryColor: WebGLUniformLocation; + private uAltView: WebGLUniformLocation; + + private affiliationTex: WebGLTexture | null = null; + private altView = false; + + // Ground/sea units (boats, trains) — render below structures + private groundVao: WebGLVertexArrayObject; + private groundBuf: DynamicInstanceBuffer; + private groundCount = 0; + + // Missiles/projectiles (nukes, shells, SAM) — render above structures + private missileVao: WebGLVertexArrayObject; + private missileBuf: DynamicInstanceBuffer; + private missileCount = 0; + + private quadBuf: WebGLBuffer; + private paletteTex: WebGLTexture; + private atlasTex: WebGLTexture; + + /** Frame tick received from renderer — drives tick-based effects */ + private frameTick = 0; + + /** unitType string → atlas column (0-11) */ + private typeToAtlasCol = new Map(); + private mapW: number; + + // Trade-friendly detection: enemy trade ships heading to a self/allied port + private localPlayerID = 0; + private friendlyOwners = new Set(); + private structures: Map = new Map(); + + constructor( + gl: WebGL2RenderingContext, + header: RendererConfig, + paletteTex: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = header.mapWidth; + this.paletteTex = paletteTex; + + // Build unitType string → atlas column mapping + for (let i = 0; i < header.unitTypes.length; i++) { + const col = UNIT_ORDER.indexOf( + header.unitTypes[i] as (typeof UNIT_ORDER)[number], + ); + if (col >= 0) { + this.typeToAtlasCol.set(header.unitTypes[i], col); + } + } + + // Compile shaders + this.program = createProgram( + gl, + shaderSrc(unitVertSrc, { ATLAS_COLS }), + shaderSrc(unitFragSrc, { PALETTE_SIZE: getPaletteSize() }), + ); + this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; + this.uTick = gl.getUniformLocation(this.program, "uTick")!; + this.uUnitSize = gl.getUniformLocation(this.program, "uUnitSize")!; + this.uFlickerSpeed = gl.getUniformLocation(this.program, "uFlickerSpeed")!; + this.uAngryColor = gl.getUniformLocation(this.program, "uAngryColor")!; + + this.uAltView = gl.getUniformLocation(this.program, "uAltView")!; + + // Texture unit bindings + gl.useProgram(this.program); + gl.uniform1i(gl.getUniformLocation(this.program, "uPalette"), 0); + gl.uniform1i(gl.getUniformLocation(this.program, "uAtlas"), 1); + gl.uniform1i(gl.getUniformLocation(this.program, "uAffiliation"), 2); + + // Create placeholder atlas texture (1x1 gray pixel) + this.atlasTex = gl.createTexture()!; + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + 1, + 1, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + new Uint8Array([128, 128, 128, 255]), + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + // Start async atlas build + this.loadAtlas(); + + // --- Shared quad buffer --- + this.quadBuf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, this.quadBuf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + + // --- Ground instance buffer + VAO --- + const groundGlBuf = gl.createBuffer()!; + this.groundBuf = new DynamicInstanceBuffer( + gl, + groundGlBuf, + 1024, + FLOATS_PER_INSTANCE, + ); + this.groundVao = createUnitVao(gl, this.quadBuf, groundGlBuf); + + // --- Missile instance buffer + VAO --- + const missileGlBuf = gl.createBuffer()!; + this.missileBuf = new DynamicInstanceBuffer( + gl, + missileGlBuf, + 512, + FLOATS_PER_INSTANCE, + ); + this.missileVao = createUnitVao(gl, this.quadBuf, missileGlBuf); + } + + private async loadAtlas(): Promise { + const img = new Image(); + img.src = unitAtlasUrl; + await img.decode(); + const gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + } + + private emitGround( + x: number, + y: number, + ownerID: number, + atlasIdx: number, + flags: number, + ): void { + this.groundBuf.ensureCapacity(this.groundCount + 1); + const off = this.groundCount * FLOATS_PER_INSTANCE; + this.groundBuf.float32[off + 0] = x; + this.groundBuf.float32[off + 1] = y; + this.groundBuf.float32[off + 2] = ownerID; + const byteOff = this.groundCount * BYTES_PER_INSTANCE; + this.groundBuf.uint8[byteOff + 12] = atlasIdx; + this.groundBuf.uint8[byteOff + 13] = flags; + this.groundCount++; + } + + private emitMissile( + x: number, + y: number, + ownerID: number, + atlasIdx: number, + flags: number, + ): void { + this.missileBuf.ensureCapacity(this.missileCount + 1); + const off = this.missileCount * FLOATS_PER_INSTANCE; + this.missileBuf.float32[off + 0] = x; + this.missileBuf.float32[off + 1] = y; + this.missileBuf.float32[off + 2] = ownerID; + const byteOff = this.missileCount * BYTES_PER_INSTANCE; + this.missileBuf.uint8[byteOff + 12] = atlasIdx; + this.missileBuf.uint8[byteOff + 13] = flags; + this.missileCount++; + } + + updateUnits(units: Map, tick: number): void { + this.frameTick = tick; + this.groundCount = 0; + this.missileCount = 0; + + for (const unit of units.values()) { + if (!unit.isActive) continue; + + let atlasIdx = this.typeToAtlasCol.get(unit.unitType); + + // Train sub-type resolution: "Train" isn't in UNIT_ORDER. + // Resolve to engine/carriage/loaded carriage based on trainType + loaded fields. + if (atlasIdx === undefined && unit.unitType === UT_TRAIN) { + const tt = unit.trainType; + if (tt === TrainType.Engine || tt === TrainType.TailEngine) { + atlasIdx = TRAIN_ENGINE_COL; + } else { + atlasIdx = unit.loaded + ? TRAIN_CARRIAGE_LOADED_COL + : TRAIN_CARRIAGE_COL; + } + } + + if (atlasIdx === undefined) continue; + + const isAngryWarship = + unit.unitType === UT_WARSHIP && unit.targetUnitId !== null; + const isFlicker = FLICKER_TYPES.has(unit.unitType); + + // Enemy trade ships heading to a self/allied port get FLAG_TRADE_FRIENDLY + // so alt-view renders them yellow instead of red. + let isTradeFriendly = false; + if ( + unit.unitType === UT_TRADE_SHIP && + unit.targetUnitId !== null && + this.localPlayerID > 0 + ) { + const targetPort = this.structures.get(unit.targetUnitId); + if (targetPort) { + const portOwner = targetPort.ownerID; + isTradeFriendly = + portOwner === this.localPlayerID || + this.friendlyOwners.has(portOwner); + } + } + + const flags = isTradeFriendly + ? FLAG_TRADE_FRIENDLY + : isAngryWarship + ? FLAG_ANGRY + : isFlicker + ? FLAG_FLICKER + : FLAG_NORMAL; + const isMissile = MISSILE_TYPES.has(unit.unitType); + + const x = unit.pos % this.mapW; + const y = (unit.pos - x) / this.mapW; + + if (isMissile) { + this.emitMissile(x, y, unit.ownerID, atlasIdx, flags); + + // Shells emit a second instance at lastPos (2-pixel trail effect) + if (unit.unitType === UT_SHELL && unit.lastPos !== unit.pos) { + const lx = unit.lastPos % this.mapW; + const ly = (unit.lastPos - lx) / this.mapW; + this.emitMissile(lx, ly, unit.ownerID, atlasIdx, flags); + } + } else { + this.emitGround(x, y, unit.ownerID, atlasIdx, flags); + } + } + + const gl = this.gl; + if (this.groundCount > 0) { + gl.bindBuffer(gl.ARRAY_BUFFER, this.groundBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.groundBuf.float32, + 0, + this.groundCount * FLOATS_PER_INSTANCE, + ); + } + if (this.missileCount > 0) { + gl.bindBuffer(gl.ARRAY_BUFFER, this.missileBuf.buffer); + gl.bufferSubData( + gl.ARRAY_BUFFER, + 0, + this.missileBuf.float32, + 0, + this.missileCount * FLOATS_PER_INSTANCE, + ); + } + } + + setAltView(active: boolean): void { + this.altView = active; + } + setAffiliationTex(tex: WebGLTexture): void { + this.affiliationTex = tex; + } + setLocalPlayer(id: number): void { + this.localPlayerID = id; + } + setAllies(allies: Set): void { + this.friendlyOwners = allies; + } + setStructures(structs: Map): void { + this.structures = structs; + } + + /** Bind shared program state + uniforms (call before drawGround/drawMissiles). */ + private bindProgram(cameraMatrix: Float32Array): void { + const gl = this.gl; + gl.useProgram(this.program); + + const us = this.settings.unit; + gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); + gl.uniform1f(this.uTick, this.frameTick); + gl.uniform1f(this.uUnitSize, us.unitSize); + gl.uniform1f(this.uFlickerSpeed, us.flickerSpeed); + gl.uniform3f(this.uAngryColor, us.angryR, us.angryG, us.angryB); + gl.uniform1i(this.uAltView, this.altView ? 1 : 0); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.atlasTex); + + if (this.affiliationTex) { + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.affiliationTex); + } + } + + /** Draw ground/sea units (boats, trains). Render below structures. */ + drawGround(cameraMatrix: Float32Array): void { + if (this.groundCount === 0) return; + this.bindProgram(cameraMatrix); + const gl = this.gl; + gl.bindVertexArray(this.groundVao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.groundCount); + } + + /** Draw missiles/projectiles (nukes, shells, SAM, MIRV warheads). Render above structures. */ + drawMissiles(cameraMatrix: Float32Array): void { + if (this.missileCount === 0) return; + this.bindProgram(cameraMatrix); + const gl = this.gl; + gl.bindVertexArray(this.missileVao); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.missileCount); + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.program); + this.groundBuf.dispose(); + this.missileBuf.dispose(); + gl.deleteBuffer(this.quadBuf); + gl.deleteVertexArray(this.groundVao); + gl.deleteVertexArray(this.missileVao); + gl.deleteTexture(this.atlasTex); + } +} diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json new file mode 100644 index 0000000000..c956c4ce8a --- /dev/null +++ b/src/client/render/gl/render-settings.json @@ -0,0 +1,316 @@ +{ + "passEnabled": { + "terrain": true, + "mapOverlay": true, + "structure": true, + "unit": true, + "name": true, + "falloutBloom": true, + "railroad": true, + "fx": true, + "bar": true, + "dayNight": true, + "nameDebug": false + }, + "falloutBloom": { + "broilSpeedCold": 0.0018, + "broilSpeedHot": 0, + "noiseFreq1": 0.059, + "noiseFreq2": 0.171, + "contrastLoCold": 0.52, + "contrastLoHot": 0, + "contrastHiCold": 1, + "contrastHiHot": 0, + "metaFreq": 0.02, + "intensityCold": 0.15, + "intensityHot": 0.6, + "metaInfluenceCold": 1, + "metaInfluenceHot": 0, + "opacityFadeEnd": 1, + "bloomR": 0.054901960784313725, + "bloomG": 0.8196078431372549, + "bloomB": 0, + "bloomCoverage": 1.1, + "heatDecayPerTick": 1 + }, + "dayNight": { + "mode": "cycle", + "cycleTicks": 6000, + "startPhase": 0, + "noonHold": 0.25, + "nightHold": 0.1, + "nightAmbient": 0.15, + "dayAmbient": 1, + "falloffPower": 2, + "falloutLightR": 0.15, + "falloutLightG": 0.95, + "falloutLightB": 0.15, + "falloutLightIntensity": 5.2, + "falloutLightThreshold": 0.01, + "emberLightR": 1, + "emberLightG": 0.4, + "emberLightB": 0.05, + "emberLightIntensity": 3, + "blurZoomDivisor": 4, + "lightRadiusMultiplier": 1 + }, + "mapOverlay": { + "trailAlpha": 0.588, + "defenseCheckerDarken": 0.7, + "charcoalBase": 0, + "charcoalVariation": 0.05, + "charcoalAlpha": 0.87, + "emberThresholdUnowned": 0.85, + "emberThresholdOwned": 0.875, + "emberFlickerSpeed": 0.12, + "emberColorDarkR": 0.6, + "emberColorDarkG": 0.15, + "emberColorDarkB": 0, + "emberColorBrightR": 1, + "emberColorBrightG": 0.5, + "emberColorBrightB": 0.05, + "emberStrengthUnowned": 0.5, + "highlightBrighten": 0.6, + "highlightThicken": 2, + "defensePostRange": 30, + "embargoTintRatio": 0.35, + "friendlyTintRatio": 0.35 + }, + "railroad": { + "railMinZoom": 2, + "railDetailZoom": 4, + "railAlpha": 1 + }, + "structure": { + "iconSize": 35, + "dotsZoomThreshold": 0.5, + "iconScaleFactorZoomedOut": 1.4, + "shapes": { + "City": { + "scale": 1, + "iconFill": 0.85 + }, + "Port": { + "scale": 1.08, + "iconFill": 0.85 + }, + "Factory": { + "scale": 1, + "iconFill": 0.85 + }, + "Defense Post": { + "scale": 1, + "iconFill": 0.8 + }, + "SAM Launcher": { + "scale": 1.4, + "iconFill": 1 + }, + "Missile Silo": { + "scale": 1.55, + "iconFill": 0.85 + } + }, + "highlightOutlineWidth": 0.04, + "highlightDimAlpha": 0.3 + }, + "structureLevel": { + "scale": 1.2, + "outlineWidth": 1.4 + }, + "bar": { + "healthBarW": 11, + "healthBarH": 3, + "healthBarOffsetY": -6, + "progressBarW": 14, + "progressBarH": 3, + "progressBarOffsetY": 6, + "borderWidth": 1, + "threshold1": 0.25, + "threshold2": 0.5, + "threshold3": 0.75, + "colorRedR": 0.91, + "colorRedG": 0.098, + "colorRedB": 0.098, + "colorOrangeR": 0.941, + "colorOrangeG": 0.478, + "colorOrangeB": 0.098, + "colorYellowR": 0.792, + "colorYellowG": 0.906, + "colorYellowB": 0.059, + "colorGreenR": 0.173, + "colorGreenG": 0.937, + "colorGreenB": 0.071 + }, + "unit": { + "unitSize": 13, + "flickerSpeed": 0.3, + "angryR": 0.784, + "angryG": 0, + "angryB": 0 + }, + "name": { + "lerpSpeed": 10, + "cullThreshold": 0.008, + "nameScaleFactor": 0.4, + "nameScaleCap": 3, + "troopSizeMultiplier": 0.6, + "outlineWidth": 1.4, + "outlineR": 1.0, + "outlineG": 1.0, + "outlineB": 1.0, + "outlineUsePlayerColor": false, + "fillUsePlayerColor": true, + "emojiRowOffset": 1.4, + "statusRowOffset": 2.5 + }, + "fx": { + "shockwaveRingWidth": 0.04, + "nukeShockwaveDurationMs": 1500, + "nukeShockwaveRadiusFactor": 1.5, + "samShockwaveDurationMs": 800, + "samShockwaveRadius": 40, + "debrisLifetimeMs": 6000, + "debrisFadeIn": 0.1, + "debrisFadeOut": 0.8, + "conquestLifetimeMs": 2500, + "conquestFadeIn": 0.1, + "conquestFadeOut": 0.6 + }, + "nukeTrajectory": { + "lineWidth": 1.25, + "outlineWidth": 1.5, + "dashTargetable": 8, + "gapTargetable": 4, + "dashUntargetable": 2, + "gapUntargetable": 6, + "lineR": 1, + "lineG": 1, + "lineB": 1, + "interceptR": 1, + "interceptG": 0.314, + "interceptB": 0.314, + "outlineR": 0.549, + "outlineG": 0.549, + "outlineB": 0.549, + "interceptOutlineR": 0.588, + "interceptOutlineG": 0.353, + "interceptOutlineB": 0.353, + "markerCircleRadius": 6, + "markerXRadius": 8 + }, + "nukeTelegraph": { + "strokeWidth": 1.5, + "dashLen": 12, + "gapLen": 6, + "rotationSpeed": 20, + "baseAlpha": 0.85, + "pulseAmplitude": 0.1, + "pulseSpeed": 3, + "fillAlphaOffset": 0.6, + "colorR": 1, + "colorG": 0, + "colorB": 0 + }, + "moveIndicator": { + "startRadius": 13, + "chevronSize": 5, + "lineWidth": 2, + "duration": 800, + "converge": 0.7 + }, + "samRadius": { + "strokeWidth": 1.5, + "dashLen": 12, + "gapLen": 6, + "rotationSpeed": 14, + "alpha": 0.8, + "outlineWidth": 0.4, + "outlineSoftness": 0.15 + }, + "bonusPopup": { + "scale": 6, + "lifetimeMs": 1500, + "riseSpeed": 3, + "yOffset": -3, + "outlineWidth": 2, + "colorR": 1, + "colorG": 1, + "colorB": 1, + "minScreenScale": 0.15, + "cullZoom": 0.3 + }, + "spawnOverlay": { + "highlightRadius": 9, + "highlightAlpha": 1.0, + "selfMinRad": 8, + "selfMaxRad": 24, + "mateMinRad": 5, + "mateMaxRad": 14, + "animSpeed": 0.005, + "gradientInnerEdge": 0.01, + "gradientSolidEnd": 0.1 + }, + "altView": { + "gridFontSize": 16, + "recolorStructures": true + }, + "lightConfigs": { + "City": { + "radius": 18, + "intensity": 1.2 + }, + "Port": { + "radius": 12, + "intensity": 1 + }, + "Factory": { + "radius": 12, + "intensity": 1 + }, + "Defense Post": { + "radius": 10, + "intensity": 0.9 + }, + "SAM Launcher": { + "radius": 10, + "intensity": 0.9 + }, + "Missile Silo": { + "radius": 10, + "intensity": 0.9 + }, + "Transport": { + "radius": 6, + "intensity": 2.7 + }, + "Trade Ship": { + "radius": 6, + "intensity": 2.7 + }, + "Warship": { + "radius": 10, + "intensity": 2.8 + }, + "Atom Bomb": { + "radius": 16, + "intensity": 1.1 + }, + "Hydrogen Bomb": { + "radius": 22, + "intensity": 1.3 + }, + "MIRV": { + "radius": 18, + "intensity": 1.2 + }, + "MIRV Warhead": { + "radius": 12, + "intensity": 1 + }, + "Train": { + "radius": 8, + "intensity": 2 + } + } +} diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts new file mode 100644 index 0000000000..bd67bfea52 --- /dev/null +++ b/src/client/render/gl/render-settings.ts @@ -0,0 +1,253 @@ +import defaults from "./render-settings.json"; + +export interface RenderSettings { + passEnabled: { + terrain: boolean; + mapOverlay: boolean; + structure: boolean; + unit: boolean; + name: boolean; + falloutBloom: boolean; + railroad: boolean; + fx: boolean; + bar: boolean; + dayNight: boolean; + nameDebug: boolean; + }; + falloutBloom: { + broilSpeedCold: number; + broilSpeedHot: number; + noiseFreq1: number; + noiseFreq2: number; + contrastLoCold: number; + contrastLoHot: number; + contrastHiCold: number; + contrastHiHot: number; + metaFreq: number; + intensityCold: number; + intensityHot: number; + metaInfluenceCold: number; + metaInfluenceHot: number; + opacityFadeEnd: number; + bloomR: number; + bloomG: number; + bloomB: number; + bloomCoverage: number; + heatDecayPerTick: number; + }; + dayNight: { + mode: "light" | "dark" | "cycle"; + cycleTicks: number; + startPhase: number; // 0–1, where 0 = noon, 0.25 = dusk, 0.5 = midnight, 0.75 = dawn + noonHold: number; // fraction of cycle held at full brightness (0–1) + nightHold: number; // fraction of cycle held at full darkness (0–1); noonHold+nightHold ≤ 1 + nightAmbient: number; + dayAmbient: number; + falloffPower: number; + falloutLightR: number; + falloutLightG: number; + falloutLightB: number; + falloutLightIntensity: number; + falloutLightThreshold: number; + emberLightR: number; + emberLightG: number; + emberLightB: number; + emberLightIntensity: number; + blurZoomDivisor: number; + lightRadiusMultiplier: number; + }; + mapOverlay: { + trailAlpha: number; + defenseCheckerDarken: number; + charcoalBase: number; + charcoalVariation: number; + charcoalAlpha: number; + emberThresholdUnowned: number; + emberThresholdOwned: number; + emberFlickerSpeed: number; + emberColorDarkR: number; + emberColorDarkG: number; + emberColorDarkB: number; + emberColorBrightR: number; + emberColorBrightG: number; + emberColorBrightB: number; + emberStrengthUnowned: number; + highlightBrighten: number; + highlightThicken: number; + defensePostRange: number; + embargoTintRatio: number; + friendlyTintRatio: number; + }; + railroad: { + railMinZoom: number; + railDetailZoom: number; + railAlpha: number; + }; + structure: { + iconSize: number; + dotsZoomThreshold: number; + iconScaleFactorZoomedOut: number; + shapes: Record; + highlightOutlineWidth: number; + highlightDimAlpha: number; + }; + structureLevel: { + scale: number; + outlineWidth: number; + }; + bar: { + healthBarW: number; + healthBarH: number; + healthBarOffsetY: number; + progressBarW: number; + progressBarH: number; + progressBarOffsetY: number; + borderWidth: number; + threshold1: number; + threshold2: number; + threshold3: number; + colorRedR: number; + colorRedG: number; + colorRedB: number; + colorOrangeR: number; + colorOrangeG: number; + colorOrangeB: number; + colorYellowR: number; + colorYellowG: number; + colorYellowB: number; + colorGreenR: number; + colorGreenG: number; + colorGreenB: number; + }; + unit: { + unitSize: number; + flickerSpeed: number; + angryR: number; + angryG: number; + angryB: number; + }; + name: { + lerpSpeed: number; + cullThreshold: number; + nameScaleFactor: number; + nameScaleCap: number; + troopSizeMultiplier: number; + outlineWidth: number; + outlineR: number; + outlineG: number; + outlineB: number; + outlineUsePlayerColor: boolean; + fillUsePlayerColor: boolean; + emojiRowOffset: number; + statusRowOffset: number; + }; + fx: { + shockwaveRingWidth: number; + nukeShockwaveDurationMs: number; + nukeShockwaveRadiusFactor: number; + samShockwaveDurationMs: number; + samShockwaveRadius: number; + debrisLifetimeMs: number; + debrisFadeIn: number; // 0–1 fraction of lifetime + debrisFadeOut: number; // 0–1 fraction of lifetime (start of fade) + conquestLifetimeMs: number; + conquestFadeIn: number; + conquestFadeOut: number; + }; + nukeTrajectory: { + lineWidth: number; // px — main line stroke width + outlineWidth: number; // px — extra width for outline behind line + dashTargetable: number; // px — dash length in targetable zone + gapTargetable: number; // px — gap length in targetable zone + dashUntargetable: number; // px — dash length in untargetable zone + gapUntargetable: number; // px — gap length in untargetable zone + lineR: number; // normal line color + lineG: number; + lineB: number; + interceptR: number; // line color after SAM intercept + interceptG: number; + interceptB: number; + outlineR: number; // outline color (normal) + outlineG: number; + outlineB: number; + interceptOutlineR: number; // outline color (after intercept) + interceptOutlineG: number; + interceptOutlineB: number; + markerCircleRadius: number; // px — zone boundary circle size + markerXRadius: number; // px — SAM intercept X size + }; + nukeTelegraph: { + strokeWidth: number; // world units — circle ring width + dashLen: number; // world units — outer ring dash length + gapLen: number; // world units — outer ring gap length + rotationSpeed: number; // outer ring rotation speed + baseAlpha: number; // base opacity (0–1) + pulseAmplitude: number; // alpha pulse ± + pulseSpeed: number; // pulse frequency (radians/sec) + fillAlphaOffset: number; // inner fill is baseAlpha minus this + colorR: number; // circle color + colorG: number; + colorB: number; + }; + moveIndicator: { + startRadius: number; // screen px — initial distance from center + chevronSize: number; // screen px — wing span + lineWidth: number; // screen px — stroke width + duration: number; // ms — total animation lifetime + converge: number; // 0–1 — fraction of radius consumed during animation + }; + samRadius: { + strokeWidth: number; // ring half-width in world units + dashLen: number; // dash length in world units + gapLen: number; // gap length in world units + rotationSpeed: number; // world units per second + alpha: number; // base opacity (0–1) + outlineWidth: number; // outline border width in world units + outlineSoftness: number; // smoothstep range (0 = hard, higher = softer) + }; + bonusPopup: { + scale: number; + lifetimeMs: number; + riseSpeed: number; + yOffset: number; + outlineWidth: number; + colorR: number; + colorG: number; + colorB: number; + minScreenScale: number; // minimum world-scale when zoomed out (prevents vanishing) + cullZoom: number; // popups hidden below this zoom level + }; + spawnOverlay: { + highlightRadius: number; // tile highlight radius (squared internally) + highlightAlpha: number; // tile highlight opacity (0–1) + selfMinRad: number; // self ring inner radius + selfMaxRad: number; // self ring outer radius + mateMinRad: number; // teammate ring inner radius + mateMaxRad: number; // teammate ring outer radius + animSpeed: number; // breathing animation speed + gradientInnerEdge: number; // static gradient inner ramp end (0–1) + gradientSolidEnd: number; // static gradient solid band end (0–1) + }; + altView: { + gridFontSize: number; + recolorStructures: boolean; + }; + lightConfigs: Record; +} + +/** Create a fresh settings object with defaults from render-settings.json. */ +export function createRenderSettings(): RenderSettings { + return JSON.parse(JSON.stringify(defaults)) as RenderSettings; +} + +/** Dump current settings to a downloadable JSON file. */ +export function dumpSettings(settings: RenderSettings): void { + const json = JSON.stringify(settings, null, 2); + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "render-settings.json"; + a.click(); + URL.revokeObjectURL(url); +} diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/renderer.ts new file mode 100644 index 0000000000..dd42383bb1 --- /dev/null +++ b/src/client/render/gl/renderer.ts @@ -0,0 +1,1136 @@ +/** + * GPURenderer v2 — normalized render pipeline. + * + * Draw order: + * DATA SYNC: tile flush → heat update → border compute + * BASE PASS (darkened by night): terrain → territory fill + fallout charcoal + * NIGHT COMPOSITE (optional): lightmap → scene × (ambient + lightmap) + * FULL BRIGHTNESS (always): borders → railroads → ground units → structures → + * structure levels → bars → bloom → trails → missiles → fx → conquest → names + */ + +import type { + AttackRingInput, + BonusEvent, + ConquestFx, + DeadUnitFx, + GhostPreviewData, + NameEntry, + NukeTelegraphData, + NukeTrajectoryData, + PlayerState, + PlayerStatic, + PlayerStatusData, + RendererConfig, + TilePair, + UnitState, +} from "../types"; +import { Camera } from "./camera"; +import type { RadialMenuItem } from "./events"; +import { BarPass } from "./passes/bar-pass"; +import { BorderComputePass } from "./passes/border-compute-pass"; +import { BorderStampPass } from "./passes/border-stamp-pass"; +import { ConquestPopupPass } from "./passes/conquest-popup-pass"; +import { CoordinateGridPass } from "./passes/coordinate-grid-pass"; +import { CrosshairPass } from "./passes/crosshair-pass"; +import { FalloutBloomPass } from "./passes/fallout-bloom-pass"; +import { FalloutLightPass } from "./passes/fallout-light-pass"; +import { FxPass } from "./passes/fx-pass"; +import { LightmapPass } from "./passes/lightmap-pass"; +import { MoveIndicatorPass } from "./passes/move-indicator-pass"; +import { NamePass } from "./passes/name-pass"; +import { NightCompositePass } from "./passes/night-composite-pass"; +import { NukeTelegraphPass } from "./passes/nuke-telegraph-pass"; +import { NukeTrajectoryPass } from "./passes/nuke-trajectory-pass"; +import { PointLightPass } from "./passes/point-light-pass"; +import { RadialMenuPass } from "./passes/radial-menu-pass"; +import { RailroadPass } from "./passes/railroad-pass"; +import { RangeCirclePass } from "./passes/range-circle-pass"; +import { SAMRadiusPass } from "./passes/sam-radius-pass"; +import { SelectionBoxPass } from "./passes/selection-box-pass"; +import type { SpawnCenter } from "./passes/spawn-overlay-pass"; +import { SpawnOverlayPass } from "./passes/spawn-overlay-pass"; +import { StructureLevelPass } from "./passes/structure-level-pass"; +import { StructurePass } from "./passes/structure-pass"; +import { TerrainPass } from "./passes/terrain-pass"; +import { TerritoryPass } from "./passes/territory-pass"; +import { TrailPass } from "./passes/trail-pass"; +import { UnitPass } from "./passes/unit-pass"; +import { createRenderSettings, type RenderSettings } from "./render-settings"; +import { AffiliationPalette } from "./utils/affiliation"; +import { buildTerrainRGBA, getPaletteSize } from "./utils/color-utils"; +import { + createTexture2D, + toScreen, + toTarget, + type RenderTarget, +} from "./utils/gl-utils"; +import { + createGPUResources, + disposeGPUResources, + type GPUResources, +} from "./utils/gpu-resources"; +import { HeatManager } from "./utils/heat-manager"; + +/** Ghost types that trigger SAM radius overlay (matches upstream SAMRadiusLayer). */ +const SAM_RADIUS_GHOST_TYPES = new Set([ + "Missile Silo", + "SAM Launcher", + "City", + "Atom Bomb", + "Hydrogen Bomb", +]); + +/** Subset for build-button hover — excludes City/Silo (SAM radii irrelevant). */ +const SAM_RADIUS_HIGHLIGHT_TYPES = new Set([ + "SAM Launcher", + "Atom Bomb", + "Hydrogen Bomb", +]); + +export class GPURenderer { + private gl: WebGL2RenderingContext; + private camera: Camera; + private res: GPUResources; + + // Passes + private terrainPass: TerrainPass; + private territoryPass: TerritoryPass; + private trailPass: TrailPass; + private borderStampPass: BorderStampPass; + private borderPass: BorderComputePass; + private bloomPass: FalloutBloomPass; + private pointLightPass: PointLightPass; + private falloutLightPass: FalloutLightPass; + private lightmapPass: LightmapPass; + private nightCompositePass: NightCompositePass; + private structurePass: StructurePass; + private structureLevelPass: StructureLevelPass; + private unitPass: UnitPass; + private namePass: NamePass; + private fxPass: FxPass; + private rangeCirclePass: RangeCirclePass; + private samRadiusPass: SAMRadiusPass; + private crosshairPass: CrosshairPass; + private railroadPass: RailroadPass; + private barPass: BarPass; + private conquestPopupPass: ConquestPopupPass; + private radialMenuPass: RadialMenuPass; + private selectionBoxPass: SelectionBoxPass; + private moveIndicatorPass: MoveIndicatorPass; + private nukeTrajectoryPass: NukeTrajectoryPass; + private nukeTelegraphPass: NukeTelegraphPass; + private heatManager: HeatManager; + private affiliationPalette: AffiliationPalette; + private coordinateGridPass: CoordinateGridPass; + private spawnOverlayPass: SpawnOverlayPass; + + private paletteTex: WebGLTexture; + private paletteData: Float32Array; + private canvas: HTMLCanvasElement; + private settings: RenderSettings; + private sceneTarget: RenderTarget; + private raf: typeof requestAnimationFrame; + private caf: typeof cancelAnimationFrame; + + private animId: number | null = null; + private frameTick = 0; + private gameTick = 0; + private mapW = 0; + private mapH = 0; + + // FPS tracking + private frameTimes: Float64Array = new Float64Array(60); + private frameIdx = 0; + private frameCount = 0; + fps = 0; + onFrame: ((ms: number) => void) | null = null; + afterRender: ((canvas: HTMLCanvasElement) => void) | null = null; + + // Hit-testing references + private lastUnits: Map = new Map(); + private lastStructures: Map = new Map(); + + // Local player relationship data (for SAM radius coloring) + private localPlayerID = 0; + private playerTeams = new Map(); // smallID → team + + // Alt-view: affiliation recoloring (space hold) + private altView = false; + // Grid-view: coordinate grid overlay (M toggle) + private gridView = false; + + // SAM radius visibility tracking (show if either source is true) + private samGhostVisible = false; + private samHighlightVisible = false; + + // Warship selection + private selectedUnitId: number | null = null; + + constructor( + canvas: HTMLCanvasElement, + header: RendererConfig, + terrainBytes: Uint8Array, + paletteData: Float32Array, + raf: typeof requestAnimationFrame = requestAnimationFrame.bind(window), + caf: typeof cancelAnimationFrame = cancelAnimationFrame.bind(window), + ) { + this.canvas = canvas; + this.settings = createRenderSettings(); + this.raf = raf; + this.caf = caf; + + const gl = canvas.getContext("webgl2", { + alpha: false, + antialias: false, + powerPreference: "high-performance", + }); + if (!gl) throw new Error("WebGL2 not supported"); + this.gl = gl; + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + + const floatExt = gl.getExtension("EXT_color_buffer_float"); + if (!floatExt) + console.warn("EXT_color_buffer_float not available — palette may fail"); + + const mapW = header.mapWidth; + const mapH = header.mapHeight; + this.mapW = mapW; + this.mapH = mapH; + + this.camera = new Camera(mapW, mapH); + + // --- Terrain (static) --- + const terrainRGBA = buildTerrainRGBA(terrainBytes, mapW, mapH); + this.terrainPass = new TerrainPass(gl, terrainRGBA, mapW, mapH); + + // --- Shared palette texture (RGBA32F, 4096×2) --- + this.paletteData = paletteData; + const palW = getPaletteSize(); + this.paletteTex = createTexture2D(gl, { + width: palW, + height: 2, + internalFormat: gl.RGBA32F, + format: gl.RGBA, + type: gl.FLOAT, + data: paletteData, + filter: gl.NEAREST, + }); + + // --- Border compute (creates its own borderTex) --- + // Need a temporary tileTex reference for border compute — we'll create + // GPUResources first, then wire everything. + // But borderPass creates its own borderTex internally, so we need to + // create GPUResources with it. Let's sequence carefully: + + // 1. Create GPUResources (creates tileTex, trailTex, heatTexA/B) + // borderTex placeholder — we'll get it from borderPass + // First create a dummy, then replace after borderPass is created. + + // Actually: borderPass creates its own internal borderTex (RGBA8). + // We need tileTex to exist before borderPass. So: + // a) Create shared resources (tileTex, trailTex, heatA/B) + // b) Create borderPass with tileTex → gives us borderTex + // c) Store borderTex in res + + // Create shared textures except borderTex + this.res = createGPUResources(gl, mapW, mapH, this.paletteTex, null!); + + // --- Border compute (needs tileTex) --- + this.borderPass = new BorderComputePass( + gl, + mapW, + mapH, + this.res.tileTex, + this.settings, + ); + this.res.borderTex = this.borderPass.getBorderTex(); + + // --- Heat manager (needs tileTex, heatTexA/B) --- + this.heatManager = new HeatManager( + gl, + mapW, + mapH, + this.res.tileTex, + this.res.heatTexA, + this.res.heatTexB, + this.settings, + ); + + // --- Territory (needs tileTex, trailTex, paletteTex) --- + this.territoryPass = new TerritoryPass( + gl, + mapW, + mapH, + this.res.tileTex, + this.res.trailTex, + this.paletteTex, + this.settings, + ); + + // --- Spawn overlay (needs tileTex) --- + this.spawnOverlayPass = new SpawnOverlayPass( + gl, + mapW, + mapH, + this.res.tileTex, + this.settings.spawnOverlay, + ); + + // --- Trail (needs trailTex, paletteTex) --- + this.trailPass = new TrailPass( + gl, + mapW, + mapH, + this.res.trailTex, + this.paletteTex, + this.settings, + ); + + // --- Border stamp (needs tileTex, paletteTex, borderTex) --- + this.borderStampPass = new BorderStampPass( + gl, + mapW, + mapH, + this.res.tileTex, + this.paletteTex, + this.res.borderTex, + this.settings, + ); + + // --- Fallout bloom (needs tileTex, heatManager) --- + this.bloomPass = new FalloutBloomPass( + gl, + mapW, + mapH, + this.res.tileTex, + this.heatManager, + this.settings, + ); + + // --- Point lights --- + this.pointLightPass = new PointLightPass( + gl, + header, + paletteData, + this.settings, + ); + + // --- Fallout light (needs tileTex, borderTex, heatManager) --- + this.falloutLightPass = new FalloutLightPass( + gl, + mapW, + mapH, + this.res.tileTex, + this.res.borderTex, + this.heatManager, + this.settings, + ); + + // --- Lightmap orchestrator --- + this.lightmapPass = new LightmapPass( + gl, + mapW, + mapH, + this.pointLightPass, + this.falloutLightPass, + this.settings, + ); + + // --- Night composite --- + this.nightCompositePass = new NightCompositePass(gl, this.settings); + + // --- Railroad (needs tileTex) --- + this.railroadPass = new RailroadPass( + gl, + mapW, + mapH, + this.res.tileTex, + this.paletteTex, + terrainBytes, + this.settings, + ); + + // --- Range circle (ghost preview radius) --- + this.rangeCirclePass = new RangeCirclePass(gl); + + // --- SAM radius overlay (dashed green circles during build mode) --- + this.samRadiusPass = new SAMRadiusPass(gl, mapW, this.settings); + this.samRadiusPass.setPaletteData(paletteData); + + // --- Crosshair (warship placement) --- + this.crosshairPass = new CrosshairPass(gl); + + // --- Remaining passes (unchanged from v1) --- + this.structurePass = new StructurePass( + gl, + header, + this.paletteTex, + this.settings, + ); + this.structureLevelPass = new StructureLevelPass(gl, header, this.settings); + this.unitPass = new UnitPass(gl, header, this.paletteTex, this.settings); + this.namePass = new NamePass(gl, header, paletteData, this.settings); + this.fxPass = new FxPass(gl, header, this.settings); + this.barPass = new BarPass(gl, header, this.settings); + this.conquestPopupPass = new ConquestPopupPass(gl, this.settings); + this.conquestPopupPass.setMapWidth(this.mapW); + this.radialMenuPass = new RadialMenuPass(gl); + this.selectionBoxPass = new SelectionBoxPass(gl); + this.moveIndicatorPass = new MoveIndicatorPass(gl, this.settings); + this.nukeTrajectoryPass = new NukeTrajectoryPass(gl, this.settings); + this.nukeTelegraphPass = new NukeTelegraphPass(gl, this.settings); + + // --- Scene capture target (for night composite) --- + const sceneTex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, sceneTex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + 1, + 1, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + const sceneFbo = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, sceneFbo); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + sceneTex, + 0, + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + this.sceneTarget = { fbo: sceneFbo, tex: sceneTex, w: 1, h: 1 }; + + // --- Alt-view passes --- + this.affiliationPalette = new AffiliationPalette(gl); + const affTex = this.affiliationPalette.getTexture(); + this.borderStampPass.setAffiliationTex(affTex); + this.unitPass.setAffiliationTex(affTex); + this.structurePass.setAffiliationTex(affTex); + this.trailPass.setAffiliationTex(affTex); + this.coordinateGridPass = new CoordinateGridPass( + gl, + mapW, + mapH, + this.settings, + ); + + for (const p of header.players) { + if (p.team !== null) this.playerTeams.set(p.smallID, p.team); + } + + this.startLoop(); + } + + private renderLoop = (): void => { + this.draw(); + this.animId = this.raf(this.renderLoop); + }; + + private startLoop(): void { + this.animId ??= this.raf(this.renderLoop); + } + + private stopLoop(): void { + if (this.animId !== null) { + this.caf(this.animId); + this.animId = null; + } + } + + // --------------------------------------------------------------------------- + // Canvas / Camera + // --------------------------------------------------------------------------- + + resize(cssWidth: number, cssHeight: number): void { + const dpr = window.devicePixelRatio || 1; + this.canvas.width = Math.round(cssWidth * dpr); + this.canvas.height = Math.round(cssHeight * dpr); + this.camera.resize(cssWidth, cssHeight); + } + + screenToWorld(screenX: number, screenY: number): { x: number; y: number } { + return this.camera.screenToWorld(screenX, screenY); + } + + worldToScreen(worldX: number, worldY: number): { x: number; y: number } { + return this.camera.worldToScreen(worldX, worldY); + } + + panTo(worldX: number, worldY: number): void { + this.camera.panTo(worldX, worldY); + } + panBy(dx: number, dy: number): void { + this.camera.panBy(dx, dy); + } + zoomTo(level: number): void { + this.camera.zoomTo(level); + } + zoomBy(factor: number): void { + this.camera.zoomBy(factor); + } + zoomAtScreen(factor: number, screenX: number, screenY: number): void { + this.camera.zoomAtScreen(factor, screenX, screenY); + } + fitMap(): void { + this.camera.fitMap(); + } + focusBBox( + minX: number, + minY: number, + maxX: number, + maxY: number, + padding?: number, + ): void { + this.camera.focusBBox(minX, minY, maxX, maxY, padding); + } + getCameraState(): { x: number; y: number; z: number } { + return { + x: this.camera.offsetX, + y: this.camera.offsetY, + z: this.camera.zoom, + }; + } + setCameraState(x: number, y: number, z: number): void { + this.camera.setCameraState(x, y, z); + } + get zoom(): number { + return this.camera.zoom; + } + + // --------------------------------------------------------------------------- + // Data upload + // --------------------------------------------------------------------------- + + applyFullFrame( + tileState: Uint16Array, + trailState: Uint8Array, + nukeEvents?: Array<{ tick: number; tiles: number[] }>, + currentTick?: number, + ): void { + this.territoryPass.uploadFullTileState(tileState); + this.territoryPass.uploadFullTrailState(trailState); + this.heatManager.resetForSeek(tileState, nukeEvents, currentTick); + } + + applyFullTiles(tileState: Uint16Array, trailState: Uint8Array): void { + this.territoryPass.uploadFullTileState(tileState); + this.territoryPass.uploadFullTrailState(trailState); + } + + applyDelta(changedTiles: TilePair[], trailState: Uint8Array): void { + this.territoryPass.uploadDeltaTiles(changedTiles); + this.territoryPass.uploadFullTrailState(trailState); + } + + uploadTileAndTrailState( + tileState: Uint16Array, + trailState: Uint8Array, + ): void { + this.territoryPass.setLiveRefs(tileState, trailState); + } + + uploadLiveDelta(tileState: Uint16Array, changedTiles: TilePair[]): void { + this.territoryPass.applyLiveDelta(tileState, changedTiles); + } + + uploadLiveTrailDelta( + trailState: Uint8Array, + dirtyRowMin: number, + dirtyRowMax: number, + ): void { + this.territoryPass.applyLiveTrailDelta( + trailState, + dirtyRowMin, + dirtyRowMax, + ); + } + + /** Re-upload palette data to the GPU texture (e.g. when players appear after initial startup). */ + updatePalette(paletteData: Float32Array): void { + const gl = this.gl; + // Mutate the stored array in-place so all passes sharing the reference see the update. + this.paletteData.set(paletteData); + // Re-upload to the GPU texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.paletteTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + getPaletteSize(), + 2, + gl.RGBA, + gl.FLOAT, + this.paletteData, + ); + // SAM radius pass stores its own copy + this.samRadiusPass.setPaletteData(this.paletteData); + } + + /** Register late-arriving players (updates palette + NamePass lookup maps). */ + addPlayers(players: PlayerStatic[], paletteData: Float32Array): void { + this.updatePalette(paletteData); + this.namePass.addPlayers(players, this.paletteData); + for (const p of players) { + if (p.team !== null) this.playerTeams.set(p.smallID, p.team); + } + } + + uploadRailroadState(data: Uint8Array): void { + this.railroadPass.uploadRailroadState(data); + } + + updateUnits(units: Map, gameTick: number): void { + this.lastUnits = units; + this.frameTick++; + this.gameTick = gameTick; + this.unitPass.updateUnits(units, this.frameTick); + this.barPass.updateBars(units, this.lastStructures, gameTick); + this.pointLightPass.updateLights(units); + this.heatManager.decayHeat(); + } + + updateNames( + names: Map, + players: Map, + snap: boolean, + statusData?: Map, + ): void { + this.namePass.updateNames(names, players, snap, statusData); + + // Extract local player's allies + teammates for SAM radius coloring + if (this.localPlayerID > 0) { + const localPS = players.get(this.localPlayerID); + const friendly = new Set(localPS?.allies ?? []); + const myTeam = this.playerTeams.get(this.localPlayerID); + if (myTeam !== undefined) { + for (const [sid, team] of this.playerTeams) { + if (team === myTeam && sid !== this.localPlayerID) friendly.add(sid); + } + } + this.samRadiusPass.setAllies(friendly); + this.unitPass.setAllies(friendly); + } + } + + updateRelations(data: Uint8Array, size: number): void { + this.borderPass.updateRelations(data, size); + this.affiliationPalette.updateRelations(data, size); + } + + updateStructures(units: Map): void { + this.lastStructures = units; + this.structurePass.updateStructures(units); + this.structureLevelPass.updateStructures(units); + this.samRadiusPass.updateStructures(units); + this.unitPass.setStructures(units); + const posts: { x: number; y: number; ownerID: number }[] = []; + const w = this.mapW; + for (const u of units.values()) { + if (u.unitType === "Defense Post" && !u.underConstruction) { + posts.push({ + x: u.pos % w, + y: (u.pos - (u.pos % w)) / w, + ownerID: u.ownerID, + }); + } + } + this.borderPass.updateDefensePosts(posts); + } + + applyDeadUnits(deadUnits: DeadUnitFx[]): void { + if (deadUnits.length > 0) this.fxPass.applyDeadUnits(deadUnits); + } + + applyRailroadDust(tileRefs: number[]): void { + if (tileRefs.length > 0) this.fxPass.applyRailroadDust(tileRefs); + } + + applyConquestEvents(events: ConquestFx[]): void { + if (events.length > 0) { + this.fxPass.applyConquestEvents(events); + this.conquestPopupPass.applyConquestEvents(events); + } + } + + applyBonusEvents(events: BonusEvent[]): void { + if (events.length === 0) return; + // In live game, filter to local player only. In replay (localPlayerID=0), show all. + const filtered = + this.localPlayerID > 0 + ? events.filter((e) => e.smallID === this.localPlayerID) + : events; + if (filtered.length > 0) this.conquestPopupPass.applyBonusEvents(filtered); + } + + updateAttackRings(rings: AttackRingInput[]): void { + this.fxPass.updateAttackRings(rings); + } + + clearFx(): void { + this.fxPass.clear(); + this.conquestPopupPass.clear(); + } + setFxTimeFn(fn: () => number): void { + this.fxPass.setTimeFn(fn); + this.conquestPopupPass.setTimeFn(fn); + } + + updateGhostPreview(data: GhostPreviewData | null): void { + this.structurePass.updateGhostPreview(data); + this.railroadPass.updateGhostPreview(data); + this.rangeCirclePass.updateGhostPreview(data); + this.crosshairPass.updateGhostPreview(data); + if (data) this.localPlayerID = data.ownerID; + this.samRadiusPass.setLocalPlayer(this.localPlayerID); + this.affiliationPalette.setLocalPlayer(this.localPlayerID); + this.unitPass.setLocalPlayer(this.localPlayerID); + this.samGhostVisible = + data !== null && SAM_RADIUS_GHOST_TYPES.has(data.ghostType); + this.samRadiusPass.setVisible( + this.samGhostVisible || this.samHighlightVisible, + ); + } + + updateNukeTrajectory(data: NukeTrajectoryData | null): void { + this.nukeTrajectoryPass.update(data); + } + + updateNukeTelegraphs(data: NukeTelegraphData[]): void { + this.nukeTelegraphPass.update(data); + } + + updateSpawnOverlay(inSpawnPhase: boolean, centers: SpawnCenter[]): void { + this.spawnOverlayPass.update(inSpawnPhase, centers); + } + + // --------------------------------------------------------------------------- + // Queries + // --------------------------------------------------------------------------- + + setHighlightOwner(ownerID: number): void { + this.borderPass.setHighlightOwner(ownerID); + } + setHighlightStructureTypes(unitTypes: string[] | null): void { + this.structurePass.setHighlightTypes(unitTypes); + this.structureLevelPass.setHighlightTypes(unitTypes); + this.samHighlightVisible = + unitTypes !== null && + unitTypes.some((t) => SAM_RADIUS_HIGHLIGHT_TYPES.has(t)); + this.samRadiusPass.setVisible( + this.samGhostVisible || this.samHighlightVisible, + ); + } + + focusOwner(ownerID: number): void { + if (ownerID !== 0) { + const bbox = this.territoryPass.getBBoxForOwner(ownerID); + if (bbox) { + this.camera.focusBBox(bbox.minX, bbox.minY, bbox.maxX, bbox.maxY); + return; + } + } + this.camera.focusBBox(0, 0, this.mapW - 1, this.mapH - 1); + } + + getOwnerAtWorld(worldX: number, worldY: number): number { + const tx = Math.floor(worldX); + const ty = Math.floor(worldY); + if (tx < 0 || ty < 0 || tx >= this.mapW || ty >= this.mapH) return 0; + return this.territoryPass.getOwnerAt(ty * this.mapW + tx); + } + + getUnitAtWorld( + worldX: number, + worldY: number, + radius: number, + ): UnitState | null { + let best: UnitState | null = null; + let bestDist = radius * radius; + const w = this.mapW; + for (const u of this.lastUnits.values()) { + const dx = (u.pos % w) - worldX; + const dy = Math.floor(u.pos / w) - worldY; + const d2 = dx * dx + dy * dy; + if (d2 < bestDist) { + bestDist = d2; + best = u; + } + } + return best; + } + + getStructureAtWorld( + worldX: number, + worldY: number, + radius: number, + ): UnitState | null { + let best: UnitState | null = null; + let bestDist = radius * radius; + const w = this.mapW; + for (const s of this.lastStructures.values()) { + const dx = (s.pos % w) - worldX; + const dy = Math.floor(s.pos / w) - worldY; + const d2 = dx * dx + dy * dy; + if (d2 < bestDist) { + bestDist = d2; + best = s; + } + } + return best; + } + + setLocalPlayerID(id: number): void { + if (id === this.localPlayerID) return; + this.localPlayerID = id; + this.samRadiusPass.setLocalPlayer(id); + this.affiliationPalette.setLocalPlayer(id); + this.unitPass.setLocalPlayer(id); + } + + setSAMRadiusVisible(visible: boolean): void { + this.samRadiusPass.setVisible(visible); + } + + setSAMPerspective(playerID: number, allies: Set): void { + this.samRadiusPass.setLocalPlayer(playerID); + this.samRadiusPass.setAllies(allies); + this.unitPass.setLocalPlayer(playerID); + this.unitPass.setAllies(allies); + } + + setSAMColorMode(mode: "perspective" | "owner"): void { + this.samRadiusPass.setColorMode(mode); + } + + setSAMAllianceClusters(clusters: Map): void { + this.samRadiusPass.setAllianceClusters(clusters); + } + + setAltView(active: boolean): void { + this.altView = active; + this.territoryPass.setAltView(active); + this.borderStampPass.setAltView(active); + this.unitPass.setAltView(active); + this.structurePass.setAltView(active); + this.trailPass.setAltView(active); + } + + setGridView(active: boolean): void { + this.gridView = active; + } + + getSettings(): RenderSettings { + return this.settings; + } + + // --------------------------------------------------------------------------- + // Radial menu + // --------------------------------------------------------------------------- + + showRadialMenu( + anchorX: number, + anchorY: number, + items: RadialMenuItem[], + centerItem?: RadialMenuItem, + ): void { + this.radialMenuPass.show(anchorX, anchorY, items, centerItem); + } + + hideRadialMenu(): void { + this.radialMenuPass.hide(); + } + openRadialSubMenu(subItems: RadialMenuItem[]): void { + this.radialMenuPass.openSubMenu(subItems); + } + goBackRadialMenu(): void { + this.radialMenuPass.goBack(); + } + setRadialMenuHover(index: number): void { + this.radialMenuPass.setHover(index); + } + radialMenuHitTest(screenX: number, screenY: number): number { + return this.radialMenuPass.hitTest(screenX, screenY); + } + get radialMenuVisible(): boolean { + return this.radialMenuPass.isVisible; + } + getRadialMenuItems(): readonly RadialMenuItem[] { + return this.radialMenuPass.getItems(); + } + getRadialMenuItemAt(index: number): RadialMenuItem | null { + return this.radialMenuPass.getItemAt(index); + } + registerRadialMenuIcons( + icons: { key: string; img: CanvasImageSource }[], + ): void { + this.radialMenuPass.registerIcons(icons); + } + + // --------------------------------------------------------------------------- + // Selection box (warship selection) + // --------------------------------------------------------------------------- + + setSelectedUnit(unitId: number | null): void { + this.selectedUnitId = unitId; + if (unitId === null) { + this.selectionBoxPass.hide(); + } + // Position + color are updated each frame in draw() from lastUnits. + } + + private updateSelectionBox(): void { + if (this.selectedUnitId === null) return; + const unit = this.lastUnits.get(this.selectedUnitId); + if (!unit || !unit.isActive) { + this.selectedUnitId = null; + this.selectionBoxPass.hide(); + return; + } + const x = unit.pos % this.mapW; + const y = Math.floor(unit.pos / this.mapW); + + // Lighten the owner's territory color by ~20% (mix toward white) + const off = unit.ownerID * 4; + const lr = Math.min( + 1, + this.paletteData[off] + (1 - this.paletteData[off]) * 0.3, + ); + const lg = Math.min( + 1, + this.paletteData[off + 1] + (1 - this.paletteData[off + 1]) * 0.3, + ); + const lb = Math.min( + 1, + this.paletteData[off + 2] + (1 - this.paletteData[off + 2]) * 0.3, + ); + + this.selectionBoxPass.update(true, x, y, lr, lg, lb); + } + + // --------------------------------------------------------------------------- + // Move indicator (warship move-target chevrons) + // --------------------------------------------------------------------------- + + showMoveIndicator(tileX: number, tileY: number, ownerID: number): void { + const off = ownerID * 4; + const r = Math.min( + 1, + this.paletteData[off] + (1 - this.paletteData[off]) * 0.3, + ); + const g = Math.min( + 1, + this.paletteData[off + 1] + (1 - this.paletteData[off + 1]) * 0.3, + ); + const b = Math.min( + 1, + this.paletteData[off + 2] + (1 - this.paletteData[off + 2]) * 0.3, + ); + this.moveIndicatorPass.show(tileX, tileY, r, g, b); + } + + // --------------------------------------------------------------------------- + // Render — normalized draw order + // --------------------------------------------------------------------------- + + draw(): void { + const now = performance.now(); + this.trackFps(now); + this.uploadTextures(); + this.computeTextures(); + this.renderFrame(); + if (this.onFrame) this.onFrame(performance.now() - now); + if (this.afterRender) this.afterRender(this.canvas); + } + + private trackFps(now: number): void { + this.frameTimes[this.frameIdx] = now; + this.frameIdx = (this.frameIdx + 1) % this.frameTimes.length; + if (this.frameCount < this.frameTimes.length) this.frameCount++; + if (this.frameCount > 1) { + const oldest = + this.frameTimes[ + (this.frameIdx - this.frameCount + this.frameTimes.length) % + this.frameTimes.length + ]; + this.fps = (this.frameCount - 1) / ((now - oldest) / 1000); + } + } + + private uploadTextures(): void { + if (this.altView) this.affiliationPalette.flush(); + if (this.territoryPass.flushTileTexture()) + this.borderPass.notifyTilesChanged(); + this.territoryPass.flushTrailTexture(); + this.heatManager.updateHeat(); + } + + private computeTextures(): void { + if (this.settings.passEnabled.mapOverlay) + this.borderPass.draw(this.frameTick); + } + + private renderFrame(): void { + const cam = this.camera.getMatrix(); + const zoom = this.camera.zoom; + const cw = this.canvas.width; + const ch = this.canvas.height; + const nightActive = this.isNightActive(); + + if (nightActive) { + this.resizeSceneTargetIfNeeded(cw, ch); + const sceneTex = toTarget(this.gl, this.sceneTarget, () => + this.drawBaseLayer(cam), + ); + const lightTex = this.lightmapPass.draw(cam, cw, ch); + toScreen(this.gl, cw, ch, () => + this.nightCompositePass.draw(this.gameTick, sceneTex, lightTex), + ); + } else { + toScreen(this.gl, cw, ch, () => this.drawBaseLayer(cam)); + } + + this.renderOverlays(cam, zoom); + } + + private isNightActive(): boolean { + const mode = this.settings.dayNight.mode; + return ( + mode === "dark" || + (mode === "cycle" && this.settings.passEnabled.dayNight) + ); + } + + private resizeSceneTargetIfNeeded(cw: number, ch: number): void { + if (this.sceneTarget.w === cw && this.sceneTarget.h === ch) return; + this.sceneTarget.w = cw; + this.sceneTarget.h = ch; + const gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, this.sceneTarget.tex); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA8, + cw, + ch, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ); + } + + private drawBaseLayer(cam: Float32Array): void { + const gl = this.gl; + const pe = this.settings.passEnabled; + gl.clearColor(0.04, 0.04, 0.06, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.disable(gl.BLEND); + if (pe.terrain) this.terrainPass.draw(cam); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + if (pe.mapOverlay) this.territoryPass.draw(cam); + } + + private renderOverlays(cam: Float32Array, zoom: number): void { + const gl = this.gl; + const pe = this.settings.passEnabled; + + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + this.spawnOverlayPass.draw(cam); + if (pe.mapOverlay) this.borderStampPass.draw(cam); + if (pe.railroad) this.railroadPass.draw(cam, zoom); + if (pe.unit) this.unitPass.drawGround(cam); + this.samRadiusPass.draw(cam); + this.rangeCirclePass.draw(cam); + this.nukeTrajectoryPass.draw(cam); + this.crosshairPass.draw(cam); + if (pe.structure) this.structurePass.draw(cam, zoom); + if (pe.structure) this.structureLevelPass.draw(cam, zoom); + if (pe.bar) this.barPass.draw(cam); + this.updateSelectionBox(); + this.selectionBoxPass.draw(cam, this.frameTick); + this.moveIndicatorPass.draw(cam, zoom); + this.nukeTelegraphPass.draw(cam); + if (pe.falloutBloom) this.bloomPass.draw(cam, this.frameTick); + if (pe.mapOverlay) this.trailPass.draw(cam); + if (pe.unit) this.unitPass.drawMissiles(cam); + + if (pe.fx) { + this.fxPass.tick(); + this.fxPass.draw(cam, zoom); + } + + this.conquestPopupPass.tick(); + this.conquestPopupPass.draw(cam, zoom); + + if (this.gridView) this.coordinateGridPass.draw(cam, zoom); + if (pe.name && !this.gridView) + this.namePass.draw( + cam, + this.nightCompositePass.getAmbient(this.gameTick), + ); + + this.radialMenuPass.draw(); + + gl.disable(gl.BLEND); + } + + // --------------------------------------------------------------------------- + // Lifecycle + // --------------------------------------------------------------------------- + + dispose(): void { + this.stopLoop(); + this.terrainPass.dispose(); + this.territoryPass.dispose(); + this.trailPass.dispose(); + this.borderStampPass.dispose(); + this.borderPass.dispose(); + this.bloomPass.dispose(); + this.pointLightPass.dispose(); + this.falloutLightPass.dispose(); + this.lightmapPass.dispose(); + this.nightCompositePass.dispose(); + this.heatManager.dispose(); + this.affiliationPalette.dispose(); + this.coordinateGridPass.dispose(); + this.spawnOverlayPass.dispose(); + this.railroadPass.dispose(); + this.rangeCirclePass.dispose(); + this.samRadiusPass.dispose(); + this.crosshairPass.dispose(); + this.structurePass.dispose(); + this.structureLevelPass.dispose(); + this.unitPass.dispose(); + this.namePass.dispose(); + this.fxPass.dispose(); + this.conquestPopupPass.dispose(); + this.radialMenuPass.dispose(); + this.selectionBoxPass.dispose(); + this.moveIndicatorPass.dispose(); + this.nukeTrajectoryPass.dispose(); + this.nukeTelegraphPass.dispose(); + this.barPass.dispose(); + disposeGPUResources(this.gl, this.res); + this.gl.deleteTexture(this.paletteTex); + this.gl.deleteFramebuffer(this.sceneTarget.fbo); + this.gl.deleteTexture(this.sceneTarget.tex); + this.lastUnits = new Map(); + this.lastStructures = new Map(); + } +} diff --git a/src/client/render/gl/settings-utils.ts b/src/client/render/gl/settings-utils.ts new file mode 100644 index 0000000000..fa48e0086a --- /dev/null +++ b/src/client/render/gl/settings-utils.ts @@ -0,0 +1,49 @@ +/** + * Utilities for RenderSettings persistence — deep-assign, deep-diff. + */ + +type Obj = Record; + +/** Recursively assign source values onto target, preserving target's structure. */ +export function deepAssign(target: Obj, source: Obj): void { + for (const key of Object.keys(source)) { + if ( + typeof source[key] === "object" && + source[key] !== null && + typeof target[key] === "object" && + target[key] !== null + ) { + deepAssign(target[key] as Obj, source[key] as Obj); + } else if (key in target) { + target[key] = source[key]; + } + } +} + +/** + * Compute a sparse deep-partial of values that differ from defaults. + * Returns `undefined` if nothing differs. + */ +export function deepDiff(defaults: Obj, current: Obj): Obj | undefined { + let result: Obj | undefined; + for (const key of Object.keys(defaults)) { + const dv = defaults[key]; + const cv = current[key]; + if ( + typeof dv === "object" && + dv !== null && + typeof cv === "object" && + cv !== null + ) { + const sub = deepDiff(dv as Obj, cv as Obj); + if (sub !== undefined) { + result ??= {}; + result[key] = sub; + } + } else if (dv !== cv) { + result ??= {}; + result[key] = cv; + } + } + return result; +} diff --git a/src/client/render/gl/shaders/bar/bar.frag.glsl b/src/client/render/gl/shaders/bar/bar.frag.glsl new file mode 100644 index 0000000000..9ebb15ab62 --- /dev/null +++ b/src/client/render/gl/shaders/bar/bar.frag.glsl @@ -0,0 +1,41 @@ +#version 300 es +precision highp float; + +uniform vec2 uBarSize; +uniform float uBorderWidth; +uniform vec3 uThresholds; +uniform vec3 uColorRed; +uniform vec3 uColorOrange; +uniform vec3 uColorYellow; +uniform vec3 uColorGreen; + +in vec2 vLocalPos; +flat in float vProgress; + +out vec4 fragColor; + +void main() { + float x = vLocalPos.x; + float y = vLocalPos.y; + float w = uBarSize.x; + float h = uBarSize.y; + + // Border on each side + float bw = uBorderWidth; + bool inBorder = x < bw || x > w - bw || y < bw || y > h - bw; + + // Colored fill region + float fillWidth = vProgress * (w - 2.0 * bw); + bool inFill = !inBorder && (x - bw) < fillWidth; + + if (inFill) { + vec3 color; + if (vProgress < uThresholds.x) color = uColorRed; + else if (vProgress < uThresholds.y) color = uColorOrange; + else if (vProgress < uThresholds.z) color = uColorYellow; + else color = uColorGreen; + fragColor = vec4(color, 1.0); + } else { + fragColor = vec4(0.0, 0.0, 0.0, 1.0); + } +} diff --git a/src/client/render/gl/shaders/bar/bar.vert.glsl b/src/client/render/gl/shaders/bar/bar.vert.glsl new file mode 100644 index 0000000000..237b906ff9 --- /dev/null +++ b/src/client/render/gl/shaders/bar/bar.vert.glsl @@ -0,0 +1,27 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; // unit quad [0,1]x[0,1] +layout(location = 1) in vec3 aInstData; // x, y, progress + +uniform mat3 uCamera; +uniform vec2 uBarSize; // (width, height) in world tiles +uniform vec2 uBarOffset; // offset from unit center in tiles + +out vec2 vLocalPos; // [0, barWidth] x [0, barHeight] +flat out float vProgress; + +void main() { + float worldX = aInstData.x; + float worldY = aInstData.y; + vProgress = aInstData.z; + + vec2 center = vec2(worldX + 0.5, worldY + 0.5); + vec2 barOrigin = center + uBarOffset; + vec2 worldPos = barOrigin + aPos * uBarSize; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vLocalPos = aPos * uBarSize; +} diff --git a/src/client/render/gl/shaders/border-compute/border-compute.frag.glsl b/src/client/render/gl/shaders/border-compute/border-compute.frag.glsl new file mode 100644 index 0000000000..c5ca55d806 --- /dev/null +++ b/src/client/render/gl/shaders/border-compute/border-compute.frag.glsl @@ -0,0 +1,123 @@ +#version 300 es +precision highp float; +precision highp usampler2D; + +uniform usampler2D uTileTex; // R16UI — tile state per cell +uniform usampler2D uRelationTex; // R8UI — relationship matrix (ownerA × ownerB) +uniform vec2 uMapSize; +uniform uint uHighlightOwner; +uniform int uHighlightThicken; // Chebyshev radius for highlight expansion +uniform float uTick; +uniform float uEmberThresholdUnowned; +uniform float uEmberThresholdOwned; +uniform float uEmberFlickerSpeed; + +// Defense post proximity — (x, y, ownerID, _) per post +uniform vec4 uDefensePosts[MAX_DEFENSE_POSTS]; +uniform int uDefensePostCount; +uniform float uDefensePostRange; + +out vec4 fragColor; + +uint getOwner(ivec2 c) { + if (c.x < 0 || c.y < 0 || c.x >= int(uMapSize.x) || c.y >= int(uMapSize.y)) + return 0u; + return texelFetch(uTileTex, c, 0).r & uint(OWNER_MASK); +} + +void main() { + ivec2 tc = ivec2(gl_FragCoord.xy); + if (tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) discard; + + uint raw = texelFetch(uTileTex, tc, 0).r; + uint owner = raw & uint(OWNER_MASK); + bool fallout = (raw & (1u << FALLOUT_BIT)) != 0u; + + // --- Border detection --- + float borderType = 0.0; // 0=interior, ~0.5=normal border, ~1.0=highlight border + uint maxRel = 0u; // 0=neutral, 1=friendly, 2=embargo + + if (owner != 0u) { + // Cardinal neighbor check (standard border) + uint n = getOwner(tc + ivec2( 0, -1)); + uint s = getOwner(tc + ivec2( 0, 1)); + uint w = getOwner(tc + ivec2(-1, 0)); + uint e = getOwner(tc + ivec2( 1, 0)); + + bool isBorder = (n != owner) || (s != owner) || (w != owner) || (e != owner); + + if (isBorder) { + borderType = 0.5; // normal border + + // Relationship lookup for each cardinal neighbor with different owner + if (n != owner && n != 0u) maxRel = max(maxRel, texelFetch(uRelationTex, ivec2(owner, n), 0).r); + if (s != owner && s != 0u) maxRel = max(maxRel, texelFetch(uRelationTex, ivec2(owner, s), 0).r); + if (w != owner && w != 0u) maxRel = max(maxRel, texelFetch(uRelationTex, ivec2(owner, w), 0).r); + if (e != owner && e != 0u) maxRel = max(maxRel, texelFetch(uRelationTex, ivec2(owner, e), 0).r); + } + + // Highlight: N-tile Chebyshev expansion + if (uHighlightOwner != 0u && owner == uHighlightOwner) { + if (isBorder) { + borderType = 1.0; // upgrade to highlight border + } else { + // Check expanding rings for any tile with different owner + for (int d = 1; d <= 10; d++) { + if (d > uHighlightThicken) break; + bool found = false; + // Check all tiles at Chebyshev distance d + for (int i = -d; i <= d; i++) { + // Top/bottom edges + if (getOwner(tc + ivec2(i, -d)) != owner) { found = true; break; } + if (getOwner(tc + ivec2(i, d)) != owner) { found = true; break; } + } + if (!found) { + for (int i = -d + 1; i <= d - 1; i++) { + // Left/right edges (excluding corners already checked) + if (getOwner(tc + ivec2(-d, i)) != owner) { found = true; break; } + if (getOwner(tc + ivec2( d, i)) != owner) { found = true; break; } + } + } + if (found) { + borderType = 1.0; // highlight border + break; + } + } + } + } + } + + // --- Defense post proximity --- + float defenseFlag = 0.0; + if (borderType > 0.0 && owner != 0u) { + float rangeSq = uDefensePostRange * uDefensePostRange; + for (int i = 0; i < MAX_DEFENSE_POSTS; i++) { + if (i >= uDefensePostCount) break; + vec4 dp = uDefensePosts[i]; + if (uint(dp.z) != owner) continue; + float dx = float(tc.x) - dp.x; + float dy = float(tc.y) - dp.y; + if (dx * dx + dy * dy <= rangeSq) { + defenseFlag = 1.0; + break; + } + } + } + + // --- Ember detection --- + float emberIntensity = 0.0; + if (fallout) { + float h = fract(sin(float(tc.x) * 12.9898 + float(tc.y) * 78.233) * 43758.5453); + float h2 = fract(sin(float(tc.x) * 63.7 + float(tc.y) * 157.3) * 23421.631); + float threshold = (owner == 0u) ? uEmberThresholdUnowned : uEmberThresholdOwned; + if (h2 > threshold) { + float flicker = max(0.0, sin(uTick * uEmberFlickerSpeed + h * 12.0) * 0.8 + 0.2); + flicker *= flicker; // sharpen + emberIntensity = flicker; + } + } + + // A = relationship: 0.0=neutral, 0.5=friendly, 1.0=embargo + float relation = float(maxRel) * 0.5; + fragColor = vec4(borderType, emberIntensity, defenseFlag, relation); +} diff --git a/src/client/render/gl/shaders/conquest-popup/conquest-popup.frag.glsl b/src/client/render/gl/shaders/conquest-popup/conquest-popup.frag.glsl new file mode 100644 index 0000000000..870ce6f0ea --- /dev/null +++ b/src/client/render/gl/shaders/conquest-popup/conquest-popup.frag.glsl @@ -0,0 +1,38 @@ +#version 300 es +precision highp float; + +uniform sampler2D uAtlas; +uniform float uDistRange; + +in vec2 vUV; +flat in float vAlpha; +flat in vec3 vColor; +flat in float vOutlineWidth; +out vec4 fragColor; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + if (vAlpha <= 0.0) discard; + + vec3 msd = texture(uAtlas, vUV).rgb; + float sd = median(msd.r, msd.g, msd.b); + + vec2 unitRange = uDistRange / vec2(textureSize(uAtlas, 0)); + vec2 screenTexSize = 1.0 / fwidth(vUV); + float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); + + float screenPxDist = screenPxRange * (sd - 0.5); + float fillAlpha = clamp(screenPxDist + 0.5, 0.0, 1.0); + + // Colored text with dark outline + float maxOutline = max(screenPxRange * 0.5 - 1.0, 0.0); + float effectiveOutline = min(vOutlineWidth, maxOutline); + float outlineDist = screenPxDist + effectiveOutline; + float outlineAlpha = clamp(outlineDist + 0.5, 0.0, 1.0); + + vec3 color = mix(vec3(0.0), vColor, fillAlpha); + fragColor = vec4(color, outlineAlpha * vAlpha); +} diff --git a/src/client/render/gl/shaders/conquest-popup/conquest-popup.vert.glsl b/src/client/render/gl/shaders/conquest-popup/conquest-popup.vert.glsl new file mode 100644 index 0000000000..7f508abbc4 --- /dev/null +++ b/src/client/render/gl/shaders/conquest-popup/conquest-popup.vert.glsl @@ -0,0 +1,89 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +// Per-instance: worldX, worldY, cursorX, charCode +layout(location = 1) in vec4 aInst; +// Per-instance: alpha, colorR, colorG, colorB +layout(location = 2) in vec4 aStyle; +// Per-instance: scale, outlineWidth +layout(location = 3) in vec2 aScaleOutline; + +uniform sampler2D uGlyphMetrics; // CHAR_RANGE x 2, RGBA32F + +uniform mat3 uCamera; +uniform float uFontSize; +uniform float uAtlasScaleH; +uniform float uBase; +uniform float uZoom; +uniform float uMinScreenScale; // minimum world-scale factor when zoomed out + +out vec2 vUV; +flat out float vAlpha; +flat out vec3 vColor; +flat out float vOutlineWidth; + +void main() { + float worldX = aInst.x; + float worldY = aInst.y; + float cursorX = aInst.z; + int charCode = int(aInst.w); + + if (charCode == 0 || aStyle.x <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vAlpha = 0.0; + vColor = vec3(1.0); + vOutlineWidth = 0.0; + return; + } + + // Per-instance scale (world units per font em). + // Zoom-aware minimum: ensure popups don't shrink below minScreenScale when + // zoomed out. effectiveScale = max(scale, minScreenScale / zoom) so that + // at low zoom the popup grows in world-space to maintain a minimum screen + // footprint. + float effectiveScale = max(aScaleOutline.x, uMinScreenScale / uZoom); + float worldScale = effectiveScale / uFontSize; + + // Glyph metrics from data texture + vec4 m0 = texelFetch(uGlyphMetrics, ivec2(charCode, 0), 0); + vec4 m1 = texelFetch(uGlyphMetrics, ivec2(charCode, 1), 0); + + float glyphW = m0.w; + float glyphH = m1.x; + float u0 = m1.y; + float v0 = m1.z; + float u1 = m1.w; + float v1 = v0 + glyphH / uAtlasScaleH; + + if (glyphW <= 0.0 || glyphH <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vAlpha = 0.0; + vColor = vec3(1.0); + vOutlineWidth = 0.0; + return; + } + + vec2 center = vec2(worldX + 0.5, worldY + 0.5); + float baselineY = -uBase * 0.5; + + vec2 glyphOrigin = vec2( + cursorX + m0.y, + baselineY + m0.z + ) * worldScale; + + vec2 glyphSize = vec2(glyphW, glyphH) * worldScale; + + vec2 worldPos = center + glyphOrigin + aPos * glyphSize; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vUV = vec2(mix(u0, u1, aPos.x), mix(v0, v1, aPos.y)); + vAlpha = aStyle.x; + vColor = aStyle.yzw; + vOutlineWidth = aScaleOutline.y; +} diff --git a/src/client/render/gl/shaders/crosshair/crosshair.frag.glsl b/src/client/render/gl/shaders/crosshair/crosshair.frag.glsl new file mode 100644 index 0000000000..430797df2d --- /dev/null +++ b/src/client/render/gl/shaders/crosshair/crosshair.frag.glsl @@ -0,0 +1,32 @@ +#version 300 es +precision highp float; + +in vec2 vLocal; // [-1, +1] + +uniform vec3 uColor; + +out vec4 fragColor; + +const float LINE_HALF_W = 0.08; // line half-width (normalized to quad) +const float GAP = 0.15; // center gap radius (normalized) +const float AA = 0.02; // anti-alias width + +void main() { + float ax = abs(vLocal.x); + float ay = abs(vLocal.y); + + // Horizontal arm: |y| < lineWidth, |x| > gap + float hMask = smoothstep(LINE_HALF_W + AA, LINE_HALF_W - AA, ay) + * smoothstep(GAP - AA, GAP + AA, ax) + * (1.0 - smoothstep(1.0 - AA, 1.0, ax)); + + // Vertical arm: |x| < lineWidth, |y| > gap + float vMask = smoothstep(LINE_HALF_W + AA, LINE_HALF_W - AA, ax) + * smoothstep(GAP - AA, GAP + AA, ay) + * (1.0 - smoothstep(1.0 - AA, 1.0, ay)); + + float mask = max(hMask, vMask); + if (mask < 0.01) discard; + + fragColor = vec4(uColor, mask * 0.9); +} diff --git a/src/client/render/gl/shaders/crosshair/crosshair.vert.glsl b/src/client/render/gl/shaders/crosshair/crosshair.vert.glsl new file mode 100644 index 0000000000..99e6c1fcb3 --- /dev/null +++ b/src/client/render/gl/shaders/crosshair/crosshair.vert.glsl @@ -0,0 +1,22 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; // [0,1] quad + +uniform mat3 uCamera; +uniform vec2 uCenter; // world tile coords +uniform float uHalfSize; // half-size in pixels (screen space) +uniform vec2 uViewport; // canvas width, height in pixels + +out vec2 vLocal; // [-1, +1] + +void main() { + vLocal = aPos * 2.0 - 1.0; + + // Project center to clip space + vec3 clip = uCamera * vec3(uCenter + 0.5, 1.0); + + // Offset in screen pixels → NDC + vec2 pixelToNDC = 2.0 / uViewport; + gl_Position = vec4(clip.xy + vLocal * uHalfSize * pixelToNDC, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/day-night/border-stamp.frag.glsl b/src/client/render/gl/shaders/day-night/border-stamp.frag.glsl new file mode 100644 index 0000000000..7d8c7a5cde --- /dev/null +++ b/src/client/render/gl/shaders/day-night/border-stamp.frag.glsl @@ -0,0 +1,79 @@ +#version 300 es +precision highp float; +precision highp usampler2D; + +uniform usampler2D uTileTex; +uniform sampler2D uPalette; +uniform sampler2D uBorderTex; // RGBA8 — border flags from BorderComputePass +uniform sampler2D uAffiliation; // 256×2 RGBA8 — affiliation colors (row 0 = border) +uniform vec2 uMapSize; +uniform int uAltView; +uniform float uHighlightBrighten; +uniform float uDefenseCheckerDarken; +uniform float uEmbargoTintRatio; +uniform float uFriendlyTintRatio; +uniform vec3 uEmberColorDark; +uniform vec3 uEmberColorBright; +uniform float uEmberStrengthUnowned; + +in vec2 vWorldPos; +out vec4 fragColor; + +void main() { + ivec2 tc = ivec2(floor(vWorldPos)); + if (tc.x < 0 || tc.y < 0 || tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) discard; + + uint raw = texelFetch(uTileTex, tc, 0).r; + uint owner = raw & uint(OWNER_MASK); + + // Read pre-computed border flags from BorderComputePass + vec4 borderData = texelFetch(uBorderTex, tc, 0); + float borderType = borderData.r; // 0=interior, ~0.5=normal, ~1.0=highlight + float emberIntensity = borderData.g; // 0–1 flicker value + bool defense = borderData.b > 0.5; // defense post proximity + float relation = borderData.a; // 0.0=neutral, ~0.5=friendly, ~1.0=embargo + + bool isBorder = borderType > 0.25; + bool isHighlightBorder = borderType > 0.75; + + // --- Border stamp: full-brightness border color --- + if (isBorder && owner != 0u) { + vec3 bc; + if (uAltView != 0) { + // Alt-view: pure affiliation color from palette row 0 + bc = texelFetch(uAffiliation, ivec2(int(owner), 0), 0).rgb; + } else { + float u = (float(owner) + 0.5) / float(PALETTE_SIZE); + bc = texture(uPalette, vec2(u, 0.75)).rgb; + if (isHighlightBorder) { + bc = mix(bc, vec3(1.0), uHighlightBrighten); + } + // Relationship tint (applied BEFORE defense checkerboard, matching game) + if (relation > 0.75) { + bc = mix(bc, vec3(1.0, 0.0, 0.0), uEmbargoTintRatio); + } else if (relation > 0.25) { + bc = mix(bc, vec3(0.0, 1.0, 0.0), uFriendlyTintRatio); + } + // Defense bonus: checkerboard darken (applied AFTER tint, matching game) + if (defense) { + bool checker = ((tc.x + tc.y) & 1) == 1; + if (checker) bc *= uDefenseCheckerDarken; + } + } + fragColor = vec4(bc, 1.0); + return; + } + + // --- Ember stamp: full-brightness ember on fallout tiles --- + if (emberIntensity > 0.0) { + float h = fract(sin(float(tc.x) * 12.9898 + float(tc.y) * 78.233) * 43758.5453); + vec3 ember = mix(uEmberColorDark, uEmberColorBright, h) * emberIntensity * uEmberStrengthUnowned; + float a = max(ember.r, max(ember.g, ember.b)); + if (a > 0.01) { + fragColor = vec4(ember, 1.0); + return; + } + } + + discard; +} diff --git a/src/client/render/gl/shaders/day-night/border-stamp.vert.glsl b/src/client/render/gl/shaders/day-night/border-stamp.vert.glsl new file mode 100644 index 0000000000..a1bc71dc34 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/border-stamp.vert.glsl @@ -0,0 +1,10 @@ +#version 300 es +precision highp float; +layout(location = 0) in vec2 aPos; +uniform mat3 uCamera; +out vec2 vWorldPos; +void main() { + vec3 clip = uCamera * vec3(aPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + vWorldPos = aPos; +} diff --git a/src/client/render/gl/shaders/day-night/composite.frag.glsl b/src/client/render/gl/shaders/day-night/composite.frag.glsl new file mode 100644 index 0000000000..611e788c24 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/composite.frag.glsl @@ -0,0 +1,18 @@ +#version 300 es +precision highp float; + +uniform sampler2D uSceneTex; +uniform sampler2D uLightTex; +uniform float uAmbient; + +in vec2 vUV; +out vec4 fragColor; + +void main() { + vec3 scene = texture(uSceneTex, vUV).rgb; + vec3 light = texture(uLightTex, vUV).rgb; + + // Scale lights inversely with ambient — invisible at full day, full strength at deep night + vec3 illumination = min(vec3(uAmbient) + light * (1.0 - uAmbient), vec3(1.2)); + fragColor = vec4(scene * illumination, 1.0); +} diff --git a/src/client/render/gl/shaders/day-night/fallout-composite.frag.glsl b/src/client/render/gl/shaders/day-night/fallout-composite.frag.glsl new file mode 100644 index 0000000000..011e09ffb8 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/fallout-composite.frag.glsl @@ -0,0 +1,8 @@ +#version 300 es +precision highp float; +uniform sampler2D uTex; +in vec2 vUV; +out vec4 fragColor; +void main() { + fragColor = texture(uTex, vUV); +} diff --git a/src/client/render/gl/shaders/day-night/fallout-composite.vert.glsl b/src/client/render/gl/shaders/day-night/fallout-composite.vert.glsl new file mode 100644 index 0000000000..0794903b25 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/fallout-composite.vert.glsl @@ -0,0 +1,11 @@ +#version 300 es +precision highp float; +layout(location = 0) in vec2 aPos; +uniform mat3 uCamera; +uniform vec2 uMapSize; +out vec2 vUV; +void main() { + vec3 clip = uCamera * vec3(aPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + vUV = aPos / uMapSize; +} diff --git a/src/client/render/gl/shaders/day-night/fallout-light.frag.glsl b/src/client/render/gl/shaders/day-night/fallout-light.frag.glsl new file mode 100644 index 0000000000..3d4246d099 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/fallout-light.frag.glsl @@ -0,0 +1,40 @@ +#version 300 es +precision highp float; +precision highp usampler2D; +uniform sampler2D uHeatTex; +uniform usampler2D uTileTex; +uniform sampler2D uBorderTex; +uniform vec2 uMapSize; +uniform vec3 uFalloutLightColor; +uniform float uFalloutLightIntensity; +uniform float uFalloutLightThreshold; +uniform vec3 uEmberLightColor; +uniform float uEmberLightIntensity; +out vec4 fragColor; +void main() { + ivec2 tc = ivec2(gl_FragCoord.xy); + if (tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) discard; + + uint raw = texelFetch(uTileTex, tc, 0).r; + bool fallout = (raw & (1u << FALLOUT_BIT)) != 0u; + if (!fallout) discard; + + float heat = texelFetch(uHeatTex, tc, 0).r; + + // Green fallout glow + vec3 light = vec3(0.0); + if (heat >= uFalloutLightThreshold) { + float fi = heat * uFalloutLightIntensity; + light += uFalloutLightColor * fi; + } + + // Ember light — read pre-computed flicker from BorderComputePass + float emberIntensity = texelFetch(uBorderTex, tc, 0).g; + if (emberIntensity > 0.0) { + light += uEmberLightColor * emberIntensity * uEmberLightIntensity; + } + + float a = max(light.r, max(light.g, light.b)); + if (a < 0.001) discard; + fragColor = vec4(light, a); +} diff --git a/src/client/render/gl/shaders/day-night/light.frag.glsl b/src/client/render/gl/shaders/day-night/light.frag.glsl new file mode 100644 index 0000000000..199e2657e0 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/light.frag.glsl @@ -0,0 +1,20 @@ +#version 300 es +precision highp float; + +in vec2 vLocalPos; +flat in vec3 vColor; +flat in float vIntensity; + +uniform float uFalloffPower; + +out vec4 fragColor; + +void main() { + float dist = length(vLocalPos) * 2.0; // [0, 1] from center to edge + if (dist > 1.0) discard; + + float falloff = pow(1.0 - dist, uFalloffPower); + + float brightness = falloff * vIntensity; + fragColor = vec4(vColor * brightness, brightness); +} diff --git a/src/client/render/gl/shaders/day-night/light.vert.glsl b/src/client/render/gl/shaders/day-night/light.vert.glsl new file mode 100644 index 0000000000..73c88a4331 --- /dev/null +++ b/src/client/render/gl/shaders/day-night/light.vert.glsl @@ -0,0 +1,29 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; // quad corner [0,1] +layout(location = 1) in vec3 aLightPosIdx; // x, y, typeIdx +layout(location = 2) in vec3 aLightColor; // r, g, b + +uniform mat3 uCamera; +uniform float uRadiusMultiplier; +uniform float uRadius[MAX_LIGHT_TYPES]; +uniform float uIntensity[MAX_LIGHT_TYPES]; + +out vec2 vLocalPos; +flat out vec3 vColor; +flat out float vIntensity; + +void main() { + int typeIdx = int(aLightPosIdx.z); + float radius = uRadius[typeIdx] * uRadiusMultiplier; + vec2 center = vec2(aLightPosIdx.x + 0.5, aLightPosIdx.y + 0.5); + vec2 worldPos = center + (aPos - 0.5) * radius * 2.0; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vLocalPos = aPos - 0.5; // [-0.5, 0.5] + vColor = aLightColor.rgb; + vIntensity = uIntensity[typeIdx]; +} diff --git a/src/client/render/gl/shaders/fallout-bloom/composite.frag.glsl b/src/client/render/gl/shaders/fallout-bloom/composite.frag.glsl new file mode 100644 index 0000000000..0ce5ef82c3 --- /dev/null +++ b/src/client/render/gl/shaders/fallout-bloom/composite.frag.glsl @@ -0,0 +1,10 @@ +#version 300 es +precision highp float; +uniform sampler2D uTex; +uniform float uBloomCoverage; +in vec2 vUV; +out vec4 fragColor; +void main() { + vec4 bloom = texture(uTex, vUV); + fragColor = bloom * uBloomCoverage; +} diff --git a/src/client/render/gl/shaders/fallout-bloom/composite.vert.glsl b/src/client/render/gl/shaders/fallout-bloom/composite.vert.glsl new file mode 100644 index 0000000000..0794903b25 --- /dev/null +++ b/src/client/render/gl/shaders/fallout-bloom/composite.vert.glsl @@ -0,0 +1,11 @@ +#version 300 es +precision highp float; +layout(location = 0) in vec2 aPos; +uniform mat3 uCamera; +uniform vec2 uMapSize; +out vec2 vUV; +void main() { + vec3 clip = uCamera * vec3(aPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + vUV = aPos / uMapSize; +} diff --git a/src/client/render/gl/shaders/fallout-bloom/extract.frag.glsl b/src/client/render/gl/shaders/fallout-bloom/extract.frag.glsl new file mode 100644 index 0000000000..e6653425dd --- /dev/null +++ b/src/client/render/gl/shaders/fallout-bloom/extract.frag.glsl @@ -0,0 +1,82 @@ +#version 300 es +precision highp float; +precision highp usampler2D; +uniform usampler2D uTileTex; +uniform vec2 uMapSize; +uniform float uTick; + +uniform float uBroilSpeedCold; +uniform float uBroilSpeedHot; +uniform float uNoiseFreq1; +uniform float uNoiseFreq2; +uniform float uContrastLoCold; +uniform float uContrastLoHot; +uniform float uContrastHiCold; +uniform float uContrastHiHot; +uniform float uMetaFreq; +uniform float uIntensityCold; +uniform float uIntensityHot; +uniform float uMetaInfluenceCold; +uniform float uMetaInfluenceHot; +uniform float uOpacityFadeEnd; +uniform vec3 uBloomColor; + +uniform sampler2D uHeatTex; + +out vec4 fragColor; + +float hash3(vec3 p) { + return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); +} +float vnoise3(vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + float n000 = hash3(i); + float n100 = hash3(i + vec3(1, 0, 0)); + float n010 = hash3(i + vec3(0, 1, 0)); + float n110 = hash3(i + vec3(1, 1, 0)); + float n001 = hash3(i + vec3(0, 0, 1)); + float n101 = hash3(i + vec3(1, 0, 1)); + float n011 = hash3(i + vec3(0, 1, 1)); + float n111 = hash3(i + vec3(1, 1, 1)); + return mix( + mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y), + mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y), + f.z); +} + +void main() { + // Tile-space: viewport is mapW x mapH, one fragment per tile. + // gl_FragCoord.xy gives exact integer tile coords — completely + // deterministic, independent of camera position/zoom. + ivec2 tc = ivec2(gl_FragCoord.xy); + if (tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) discard; + + uint raw = texelFetch(uTileTex, tc, 0).r; + if ((raw & (1u << FALLOUT_BIT)) == 0u) discard; + + float heat = texelFetch(uHeatTex, tc, 0).r; + vec2 tileCenter = vec2(tc) + 0.5; + + float speed = mix(uBroilSpeedCold, uBroilSpeedHot, heat); + float t = uTick * speed; + + float n1 = vnoise3(vec3(tileCenter * uNoiseFreq1, t)); + float n2 = vnoise3(vec3(tileCenter * uNoiseFreq2, t * 1.3)); + float broil = n1 * 0.6 + n2 * 0.4; + + float lo = mix(uContrastLoCold, uContrastLoHot, heat); + float hi = mix(uContrastHiCold, uContrastHiHot, heat); + broil = smoothstep(lo, hi, broil); + + float meta = vnoise3(vec3(tileCenter * uMetaFreq, t * 0.5)); + + float baseIntensity = mix(uIntensityCold, uIntensityHot, heat); + float metaInfluence = mix(uMetaInfluenceCold, uMetaInfluenceHot, heat); + float intensity = baseIntensity * mix(1.0, meta, metaInfluence); + + float opacity = smoothstep(0.0, uOpacityFadeEnd, heat); + + fragColor = vec4(uBloomColor, 1.0) * broil * intensity * opacity; +} diff --git a/src/client/render/gl/shaders/fallout-bloom/heat-decay.frag.glsl b/src/client/render/gl/shaders/fallout-bloom/heat-decay.frag.glsl new file mode 100644 index 0000000000..41519a9837 --- /dev/null +++ b/src/client/render/gl/shaders/fallout-bloom/heat-decay.frag.glsl @@ -0,0 +1,30 @@ +#version 300 es +precision highp float; +precision highp usampler2D; +uniform sampler2D uHeatTex; +uniform usampler2D uTileTex; +uniform usampler2D uPrevTileTex; +uniform vec2 uMapSize; +uniform float uDecay; +out vec4 fragColor; +void main() { + ivec2 tc = ivec2(gl_FragCoord.xy); + if (tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) discard; + + float heat = texelFetch(uHeatTex, tc, 0).r; + uint curr = texelFetch(uTileTex, tc, 0).r; + uint prev = texelFetch(uPrevTileTex, tc, 0).r; + + bool wasFallout = (prev & (1u << FALLOUT_BIT)) != 0u; + bool isFallout = (curr & (1u << FALLOUT_BIT)) != 0u; + + if (isFallout && !wasFallout) { + heat = 1.0; + } else if (!isFallout && wasFallout) { + heat = 0.0; + } else { + heat = max(0.0, heat - uDecay / 255.0); + } + + fragColor = vec4(heat, 0.0, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/fx/attack-ring.frag.glsl b/src/client/render/gl/shaders/fx/attack-ring.frag.glsl new file mode 100644 index 0000000000..8dd9b39694 --- /dev/null +++ b/src/client/render/gl/shaders/fx/attack-ring.frag.glsl @@ -0,0 +1,40 @@ +#version 300 es +precision highp float; + +uniform float uTime; // seconds, for rotation +uniform float uRingWidth; // line thickness in normalized coords + +in vec2 vLocalPos; +flat in float vAlpha; + +out vec4 fragColor; + +const float INNER_R = 0.5; +const float OUTER_R = 0.8; +const float INNER_DASHES = 8.0; +const float OUTER_DASHES = 2.0; +const float PI = 3.14159265; + +void main() { + float dist = length(vLocalPos); + float angle = atan(vLocalPos.y, vLocalPos.x); + + // Inner ring — thin, many dashes, rotating clockwise + float innerDist = abs(dist - INNER_R); + float innerRing = 1.0 - smoothstep(0.0, uRingWidth * 2.0, innerDist); + float innerAngle = angle + uTime * 1.2; + float innerDash = smoothstep(0.4, 0.5, abs(fract(innerAngle * INNER_DASHES / (2.0 * PI)) - 0.5) * 2.0); + innerRing *= innerDash; + + // Outer ring — thick, few dashes, counter-rotating + float outerDist = abs(dist - OUTER_R); + float outerRing = 1.0 - smoothstep(0.0, uRingWidth * 3.0, outerDist); + float outerAngle = angle - uTime * 0.6; + float outerDash = smoothstep(0.3, 0.4, abs(fract(outerAngle * OUTER_DASHES / (2.0 * PI)) - 0.5) * 2.0); + outerRing *= outerDash; + + float ring = max(innerRing, outerRing); + if (ring < 0.01) discard; + + fragColor = vec4(1.0, 0.0, 0.0, ring * vAlpha); +} diff --git a/src/client/render/gl/shaders/fx/attack-ring.vert.glsl b/src/client/render/gl/shaders/fx/attack-ring.vert.glsl new file mode 100644 index 0000000000..3c66df3976 --- /dev/null +++ b/src/client/render/gl/shaders/fx/attack-ring.vert.glsl @@ -0,0 +1,27 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec3 aInstData; // x, y, alpha + +uniform mat3 uCamera; +uniform float uTilesPerPx; + +out vec2 vLocalPos; +flat out float vAlpha; + +// Upstream outer ring = 16 screen-px; quad needs headroom for SDF AA. +const float RING_SCREEN_PX = 20.0; + +void main() { + vec2 center = vec2(aInstData.x + 0.5, aInstData.y + 0.5); + vAlpha = aInstData.z; + + float worldRadius = RING_SCREEN_PX * uTilesPerPx; + vec2 worldPos = center + (aPos - 0.5) * worldRadius * 2.0; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vLocalPos = (aPos - 0.5) * 2.0; +} diff --git a/src/client/render/gl/shaders/fx/shockwave.frag.glsl b/src/client/render/gl/shaders/fx/shockwave.frag.glsl new file mode 100644 index 0000000000..81bb071b12 --- /dev/null +++ b/src/client/render/gl/shaders/fx/shockwave.frag.glsl @@ -0,0 +1,17 @@ +#version 300 es +precision highp float; + +uniform float uRingWidth; + +in vec2 vLocalPos; +flat in float vAlpha; + +out vec4 fragColor; + +void main() { + float dist = length(vLocalPos); + float ringDist = abs(dist - 1.0); + float ring = 1.0 - smoothstep(0.0, uRingWidth, ringDist); + if (ring < 0.01) discard; + fragColor = vec4(1.0, 1.0, 1.0, ring * vAlpha); +} diff --git a/src/client/render/gl/shaders/fx/shockwave.vert.glsl b/src/client/render/gl/shaders/fx/shockwave.vert.glsl new file mode 100644 index 0000000000..e27aa4037f --- /dev/null +++ b/src/client/render/gl/shaders/fx/shockwave.vert.glsl @@ -0,0 +1,27 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec4 aInstData; // x, y, radius, alpha + +uniform mat3 uCamera; + +out vec2 vLocalPos; +flat out float vAlpha; + +// Extra margin so the ring's outer feathering isn't clipped at the quad edge. +const float MARGIN = 1.1; // 10% beyond ring radius + +void main() { + vec2 center = vec2(aInstData.x + 0.5, aInstData.y + 0.5); + float r = aInstData.z; + vAlpha = aInstData.w; + + vec2 worldPos = center + (aPos - 0.5) * r * 2.0 * MARGIN; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + // Scale vLocalPos by the same margin so dist=1.0 stays at the ring radius + vLocalPos = (aPos - 0.5) * 2.0 * MARGIN; +} diff --git a/src/client/render/gl/shaders/fx/sprite.frag.glsl b/src/client/render/gl/shaders/fx/sprite.frag.glsl new file mode 100644 index 0000000000..f076ea6896 --- /dev/null +++ b/src/client/render/gl/shaders/fx/sprite.frag.glsl @@ -0,0 +1,15 @@ +#version 300 es +precision highp float; + +uniform sampler2D uAtlas; + +in vec2 vAtlasUV; +flat in float vAlpha; + +out vec4 fragColor; + +void main() { + vec4 texel = texture(uAtlas, vAtlasUV); + if (texel.a < 0.01) discard; + fragColor = vec4(texel.rgb, texel.a * vAlpha); +} diff --git a/src/client/render/gl/shaders/fx/sprite.vert.glsl b/src/client/render/gl/shaders/fx/sprite.vert.glsl new file mode 100644 index 0000000000..56f83aeb31 --- /dev/null +++ b/src/client/render/gl/shaders/fx/sprite.vert.glsl @@ -0,0 +1,33 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec3 aInstPos; // x, y, fxType +layout(location = 2) in vec2 aInstFlags; // frameIdx (uint8), alpha (uint8) + +uniform mat3 uCamera; +uniform vec4 uFxUV[FX_TYPE_COUNT]; // vTop, vSpan, uFrameSpan, 0 +uniform vec4 uFxWorld[FX_TYPE_COUNT]; // worldW, worldH, 0, 0 + +out vec2 vAtlasUV; +flat out float vAlpha; + +void main() { + int type = int(aInstPos.z + 0.5); + float frameIdx = floor(aInstFlags.x + 0.5); + float alpha = aInstFlags.y / 255.0; + + vec4 uv = uFxUV[type]; + vec4 world = uFxWorld[type]; + + vec2 center = vec2(aInstPos.x + 0.5, aInstPos.y + 0.5); + vec2 worldPos = center + (aPos - 0.5) * vec2(world.x, world.y); + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + float u = (frameIdx + aPos.x) * uv.z; + float v = uv.x + aPos.y * uv.y; + vAtlasUV = vec2(u, v); + vAlpha = alpha; +} diff --git a/src/client/render/gl/shaders/glsl.d.ts b/src/client/render/gl/shaders/glsl.d.ts new file mode 100644 index 0000000000..03d75630b4 --- /dev/null +++ b/src/client/render/gl/shaders/glsl.d.ts @@ -0,0 +1,4 @@ +declare module "*.glsl?raw" { + const source: string; + export default source; +} diff --git a/src/client/render/gl/shaders/grid/grid.frag.glsl b/src/client/render/gl/shaders/grid/grid.frag.glsl new file mode 100644 index 0000000000..7174d0efac --- /dev/null +++ b/src/client/render/gl/shaders/grid/grid.frag.glsl @@ -0,0 +1,104 @@ +#version 300 es +precision highp float; + +uniform vec2 uMapSize; +uniform float uCellSize; +uniform float uZoom; +uniform float uFontSize; +uniform sampler2D uGlyphTex; + +in vec2 vWorldPos; +out vec4 fragColor; + +const float GLYPH_COUNT = 36.0; // 0-9, A-Z + +void main() { + vec2 wp = vWorldPos; + if (wp.x < 0.0 || wp.y < 0.0 || wp.x >= uMapSize.x || wp.y >= uMapSize.y) + discard; + + float cs = uCellSize; + float px = 1.0 / uZoom; // 1 screen pixel in world units + float lineW = px * 1.25; + + // Grid cell index + position within cell + int cellCol = int(floor(wp.x / cs)); + int cellRow = int(floor(wp.y / cs)); + float localX = wp.x - float(cellCol) * cs; + float localY = wp.y - float(cellRow) * cs; + + // --- Grid lines (at cell boundaries) --- + if (localX < lineW || localY < lineW) { + fragColor = vec4(1.0, 1.0, 1.0, 0.35); + return; + } + + // --- Labels (only when cells are large enough on screen) --- + float cellScreenPx = cs * uZoom; + if (cellScreenPx < 60.0) discard; + + float fontSize = clamp(uFontSize + (uZoom - 1.0) * 1.2, uFontSize * 0.9, uFontSize * 1.6); + float gw = fontSize * 0.6 * px; // glyph width in world units + float gh = fontSize * px; // glyph height + float pad = 8.0 * px; // padding from cell corner + float bgPad = 2.0 * px; // background extends beyond text + + float lx = localX - pad; + float ly = localY - pad; + + // Compute label characters: row alpha + col digits + // Atlas indices: 0-9 = digits '0'-'9', 10-35 = letters 'A'-'Z' + int c0, c1 = -1, c2 = -1, c3 = -1; + int nc; + + // Row part (A, B, ..., Z, AA, AB, ...) + if (cellRow < 26) { + c0 = cellRow + 10; + nc = 1; + } else { + c0 = (cellRow / 26 - 1) + 10; + c1 = (cellRow % 26) + 10; + nc = 2; + } + + // Col part (1-indexed: 1, 2, ..., 50) + int colNum = cellCol + 1; + if (nc == 1) { + if (colNum < 10) { c1 = colNum; nc = 2; } + else { c1 = colNum / 10; c2 = colNum % 10; nc = 3; } + } else { + if (colNum < 10) { c2 = colNum; nc = 3; } + else { c2 = colNum / 10; c3 = colNum % 10; nc = 4; } + } + + float totalW = float(nc) * gw; + + // Check label background area (text + padding) + if (lx < -bgPad || ly < -bgPad || lx >= totalW + bgPad || ly >= gh + bgPad) + discard; + + // Check if on actual glyph + if (lx >= 0.0 && ly >= 0.0 && lx < totalW && ly < gh) { + int ci = int(floor(lx / gw)); + if (ci < nc) { + int g; + if (ci == 0) g = c0; + else if (ci == 1) g = c1; + else if (ci == 2) g = c2; + else g = c3; + + float cu = fract(lx / gw); + float cv = ly / gh; + float au = (float(g) + cu) / GLYPH_COUNT; + float mask = texture(uGlyphTex, vec2(au, cv)).r; + + if (mask > 0.3) { + fragColor = vec4(1.0, 1.0, 1.0, 0.9); + return; + } + } + } + + // Background behind label + fragColor = vec4(0.08, 0.08, 0.08, 0.7); +} diff --git a/src/client/render/gl/shaders/map-overlay/overlay.vert.glsl b/src/client/render/gl/shaders/map-overlay/overlay.vert.glsl new file mode 100644 index 0000000000..80f9eb8dc1 --- /dev/null +++ b/src/client/render/gl/shaders/map-overlay/overlay.vert.glsl @@ -0,0 +1,14 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +uniform mat3 uCamera; + +out vec2 vWorldPos; + +void main() { + vec3 clip = uCamera * vec3(aPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + vWorldPos = aPos; +} diff --git a/src/client/render/gl/shaders/map-overlay/territory.frag.glsl b/src/client/render/gl/shaders/map-overlay/territory.frag.glsl new file mode 100644 index 0000000000..94c0579b47 --- /dev/null +++ b/src/client/render/gl/shaders/map-overlay/territory.frag.glsl @@ -0,0 +1,42 @@ +#version 300 es +precision highp float; +precision highp usampler2D; + +uniform usampler2D uTileTex; // R16UI — tile state per cell +uniform sampler2D uPalette; // RGBA32F — player colors + +uniform vec2 uMapSize; +uniform int uAltView; +uniform float uCharcoalBase; +uniform float uCharcoalVariation; +uniform float uCharcoalAlpha; + +in vec2 vWorldPos; +out vec4 fragColor; + +void main() { + ivec2 tc = ivec2(floor(vWorldPos)); + if (tc.x < 0 || tc.y < 0 || tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) + discard; + + uint raw = texelFetch(uTileTex, tc, 0).r; + uint owner = raw & uint(OWNER_MASK); + bool fallout = (raw & (1u << FALLOUT_BIT)) != 0u; + + if (owner == 0u && !fallout) discard; + + // Alt-view: hide territory fill, keep fallout charcoal + if (uAltView != 0 && owner != 0u) discard; + + // --- Fallout charcoal ground (unowned) --- + if (owner == 0u && fallout) { + float h = fract(sin(float(tc.x) * 12.9898 + float(tc.y) * 78.233) * 43758.5453); + float charcoal = uCharcoalBase + h * uCharcoalVariation; + fragColor = vec4(vec3(charcoal), uCharcoalAlpha); + return; + } + + // --- Territory fill (owned) --- + float u = (float(owner) + 0.5) / float(PALETTE_SIZE); + fragColor = texture(uPalette, vec2(u, 0.25)); +} diff --git a/src/client/render/gl/shaders/map-overlay/trail.frag.glsl b/src/client/render/gl/shaders/map-overlay/trail.frag.glsl new file mode 100644 index 0000000000..45d8328194 --- /dev/null +++ b/src/client/render/gl/shaders/map-overlay/trail.frag.glsl @@ -0,0 +1,31 @@ +#version 300 es +precision highp float; +precision highp usampler2D; + +uniform usampler2D uTrailTex; // R8UI — trail ownerID per cell (0 = none) +uniform sampler2D uPalette; // RGBA32F — player colors +uniform sampler2D uAffiliation; // RGBA8 — affiliation colors (row 0 = border, row 1 = unit) +uniform vec2 uMapSize; +uniform float uTrailAlpha; +uniform int uAltView; + +in vec2 vWorldPos; +out vec4 fragColor; + +void main() { + ivec2 tc = ivec2(floor(vWorldPos)); + if (tc.x < 0 || tc.y < 0 || tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) + discard; + + uint trailOwner = texelFetch(uTrailTex, tc, 0).r; + if (trailOwner == 0u) discard; + + vec3 color; + if (uAltView != 0) { + color = texelFetch(uAffiliation, ivec2(int(trailOwner), 1), 0).rgb; + } else { + float u = (float(trailOwner) + 0.5) / float(PALETTE_SIZE); + color = texture(uPalette, vec2(u, 0.25)).rgb; + } + fragColor = vec4(color, uTrailAlpha); +} diff --git a/src/client/render/gl/shaders/move-indicator/move-indicator.frag.glsl b/src/client/render/gl/shaders/move-indicator/move-indicator.frag.glsl new file mode 100644 index 0000000000..f347a311df --- /dev/null +++ b/src/client/render/gl/shaders/move-indicator/move-indicator.frag.glsl @@ -0,0 +1,62 @@ +#version 300 es +precision highp float; + +in vec2 vLocal; // [-1, +1] over ±HALF tiles + +uniform float uElapsed; // wall-clock ms since activation +uniform vec3 uColor; // RGB [0-1] +uniform float uPxPerTile; // camera zoom (pixels per tile) +uniform float uStartRadius; // screen px +uniform float uChevronSize; // screen px +uniform float uLineWidth; // screen px +uniform float uDuration; // ms +uniform float uConverge; // 0–1 + +out vec4 fragColor; + +const float HALF = 16.0; // quad half-size in tiles (must match vertex shader) + +// SDF: distance to a V-chevron pointing in +Y, centered at origin. +// The chevron has wings at (±w, -wingOff) meeting tip at (0, +tipOff). +float chevronSDF(vec2 p, float w, float tipOff, float wingOff) { + p.x = abs(p.x); + vec2 a = vec2(w, -wingOff); + vec2 b = vec2(0.0, tipOff); + vec2 ab = b - a; + float t = clamp(dot(p - a, ab) / dot(ab, ab), 0.0, 1.0); + return length(p - a - ab * t); +} + +void main() { + float t = uElapsed / uDuration; + if (t >= 1.0) discard; + + // Convert vLocal to screen pixels relative to center + float px = vLocal.x * HALF * uPxPerTile; + float py = vLocal.y * HALF * uPxPerTile; + + // Scale factor (matches game: grows above zoom 10) + float sc = uPxPerTile > 10.0 ? 1.0 + (uPxPerTile - 10.0) / 10.0 : 1.0; + + float radius = uStartRadius * sc * (1.0 - t * uConverge); + float cs = uChevronSize * sc; + float tipOff = cs * 0.4; + float wingOff = cs * 0.6; + float w = cs; // wing half-width + + // 4 chevrons pointing inward + float d = chevronSDF(vec2(px, -(py - radius)), w, tipOff, wingOff); + d = min(d, chevronSDF(vec2(px, py + radius), w, tipOff, wingOff)); + d = min(d, chevronSDF(vec2(py, -(px - radius)), w, tipOff, wingOff)); + d = min(d, chevronSDF(vec2(py, px + radius), w, tipOff, wingOff)); + + // Anti-aliased stroke (in screen pixels) + float half_w = uLineWidth * sc * 0.5; + float aa = 1.0; + float mask = 1.0 - smoothstep(half_w - aa, half_w + aa, d); + + if (mask < 0.01) discard; + + float alpha = 1.0 - t; + fragColor = vec4(uColor, alpha * mask); +} diff --git a/src/client/render/gl/shaders/move-indicator/move-indicator.vert.glsl b/src/client/render/gl/shaders/move-indicator/move-indicator.vert.glsl new file mode 100644 index 0000000000..aaf41ed736 --- /dev/null +++ b/src/client/render/gl/shaders/move-indicator/move-indicator.vert.glsl @@ -0,0 +1,20 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +uniform mat3 uCamera; +uniform vec2 uCenter; // world-space tile center + +out vec2 vLocal; // [-1, +1] local quad space + +void main() { + vLocal = aPos * 2.0 - 1.0; + + // Quad covers ±16 tiles around center (enough for the chevrons) + float r = 16.0; + vec2 world = uCenter + 0.5 + vLocal * r; + + vec3 clip = uCamera * vec3(world, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/name/debug-box.frag.glsl b/src/client/render/gl/shaders/name/debug-box.frag.glsl new file mode 100644 index 0000000000..6c8fb4b740 --- /dev/null +++ b/src/client/render/gl/shaders/name/debug-box.frag.glsl @@ -0,0 +1,32 @@ +#version 300 es +precision highp float; + +in vec2 vUV; +flat in int vBoxType; +flat in vec4 vColor; + +out vec4 fragColor; + +void main() { + if (vColor.a <= 0.0) discard; + + if (vBoxType == 2) { + // Center crosshair — draw a + shape, discard the four corner quadrants + float cx = abs(vUV.x - 0.5); + float cy = abs(vUV.y - 0.5); + // Each arm is 0.15 wide (30% of half-width) + if (cx > 0.15 && cy > 0.15) discard; + fragColor = vColor; + } else { + // Wireframe border for name/flag boxes + float borderWidth = 1.5; + vec2 pixelSize = fwidth(vUV); + vec2 border = borderWidth * pixelSize; + + if (vUV.x > border.x && vUV.x < 1.0 - border.x && + vUV.y > border.y && vUV.y < 1.0 - border.y) { + discard; + } + fragColor = vColor; + } +} diff --git a/src/client/render/gl/shaders/name/debug-box.vert.glsl b/src/client/render/gl/shaders/name/debug-box.vert.glsl new file mode 100644 index 0000000000..2ec7fdcd67 --- /dev/null +++ b/src/client/render/gl/shaders/name/debug-box.vert.glsl @@ -0,0 +1,108 @@ +#version 300 es +precision highp float; +precision highp int; + +layout(location = 0) in vec2 aPos; // unit quad [0,0]→[1,1] + +uniform sampler2D uPlayerData; +uniform mat3 uCamera; +uniform float uTime; +uniform float uLerpSpeed; +uniform float uCullThreshold; +uniform float uFontSize; +uniform float uFontBase; +uniform float uNameScaleFactor; +uniform float uNameScaleCap; + +// Flag layout (for computing flag box) +uniform float uFlagCellW; +uniform float uFlagCellH; + +out vec2 vUV; +flat out int vBoxType; // 0=name, 1=flag, 2=center +flat out vec4 vColor; + +void main() { + // 3 debug boxes per player: 0=name, 1=flag, 2=center crosshair + int playerIdx = gl_InstanceID / 3; + int boxType = gl_InstanceID - playerIdx * 3; + + vec4 pd0 = texelFetch(uPlayerData, ivec2(0, playerIdx), 0); + vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); + vec4 pd3 = texelFetch(uPlayerData, ivec2(3, playerIdx), 0); + vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); + + // Skip dead players + if (pd1.w <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vBoxType = -1; + vColor = vec4(0.0); + return; + } + + // Lerped world position (same as name.vert.glsl) + float elapsed = uTime - pd0.w; + float t = clamp(1.0 - exp(-uLerpSpeed * elapsed), 0.0, 1.0); + float wx = mix(pd0.x, pd1.x, t); + float wy = mix(pd0.y, pd1.y, t); + float ws = mix(pd0.z, pd1.z, t); + + // Sizing pipeline (must match name.vert.glsl exactly) + float baseSize = max(1.0, floor(ws)); + float nameSize = max(4.0, floor(baseSize * uNameScaleFactor)); + float nameScale = min(baseSize * 0.25, uNameScaleCap); + float nameWorldScale = (nameSize * nameScale) / uFontSize; + + // Zoom-based culling + float cameraScale = length(vec2(uCamera[0][0], uCamera[1][0])); + float screenSize = nameWorldScale * uFontBase * cameraScale; + if (screenSize < uCullThreshold) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vBoxType = -1; + vColor = vec4(0.0); + return; + } + + float nameHalfWidth = pd3.w; + + vec2 boxMin, boxMax; + + if (boxType == 0) { + // Name text bounding box (green) + float halfW = nameHalfWidth * nameWorldScale; + float halfH = uFontBase * nameWorldScale * 0.5; + boxMin = vec2(wx - halfW, wy - halfH); + boxMax = vec2(wx + halfW, wy + halfH); + vColor = vec4(0.0, 1.0, 0.0, 0.9); + } else if (boxType == 1) { + // Flag bounding box (yellow) + float flagIdx = pd4.x; + if (flagIdx < 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vBoxType = -1; + vColor = vec4(0.0); + return; + } + float halfW = nameHalfWidth * nameWorldScale; + float flagWorldH = uFontBase * nameWorldScale * 1.2; + float flagWorldW = flagWorldH * (uFlagCellW / uFlagCellH); + boxMin = vec2(wx - halfW - flagWorldW, wy - flagWorldH * 0.5); + boxMax = vec2(wx - halfW, wy + flagWorldH * 0.5); + vColor = vec4(1.0, 1.0, 0.0, 0.9); + } else { + // Center crosshair (cyan) — fixed world size proportional to name + float arm = uFontBase * nameWorldScale * 0.3; + boxMin = vec2(wx - arm, wy - arm); + boxMax = vec2(wx + arm, wy + arm); + vColor = vec4(0.0, 1.0, 1.0, 1.0); + } + + vUV = aPos; + vBoxType = boxType; + vec2 worldPos = mix(boxMin, boxMax, aPos); + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/name/icon.frag.glsl b/src/client/render/gl/shaders/name/icon.frag.glsl new file mode 100644 index 0000000000..050b83984d --- /dev/null +++ b/src/client/render/gl/shaders/name/icon.frag.glsl @@ -0,0 +1,24 @@ +#version 300 es +precision highp float; + +uniform sampler2D uFlagAtlas; +uniform sampler2D uEmojiAtlas; + +in vec2 vUV; +flat in int vIconType; // 0 = flag, 1 = emoji, -1 = discard + +out vec4 fragColor; + +void main() { + if (vIconType < 0) discard; + + vec4 texel; + if (vIconType == 0) { + texel = texture(uFlagAtlas, vUV); + } else { + texel = texture(uEmojiAtlas, vUV); + } + + if (texel.a < 0.01) discard; + fragColor = texel; +} diff --git a/src/client/render/gl/shaders/name/icon.vert.glsl b/src/client/render/gl/shaders/name/icon.vert.glsl new file mode 100644 index 0000000000..1caad7ae1b --- /dev/null +++ b/src/client/render/gl/shaders/name/icon.vert.glsl @@ -0,0 +1,154 @@ +#version 300 es +precision highp float; +precision highp int; + +// Unit quad vertex position [0,0]→[1,1] +layout(location = 0) in vec2 aPos; + +// Data textures (shared with name shader) +uniform sampler2D uPlayerData; // PLAYER_DATA_COLS × MAX_PLAYERS, RGBA32F + +// Uniforms +uniform mat3 uCamera; +uniform float uTime; +uniform float uLerpSpeed; +uniform float uCullThreshold; +uniform float uNameScaleFactor; +uniform float uNameScaleCap; +uniform float uFontSize; // atlas reference font size (same as name shader's uFontSize) +uniform float uFontBase; // atlas baseline height (same as name shader's uBase) + +// Flag atlas layout +uniform float uFlagCellW; // texels per flag cell (width) +uniform float uFlagCellH; // texels per flag cell (height) +uniform float uFlagCols; // columns in flag atlas +uniform float uFlagAtlasW; // flag atlas texture width +uniform float uFlagAtlasH; // flag atlas texture height + +// Emoji atlas layout +uniform float uEmojiCell; // texels per emoji cell (square) +uniform float uEmojiCols; // columns in emoji atlas +uniform float uEmojiAtlasW; // emoji atlas texture width +uniform float uEmojiAtlasH; // emoji atlas texture height + +// Row offset (multiples of uFontBase * nameWorldScale) +uniform float uEmojiRowOffset; + +out vec2 vUV; +flat out int vIconType; // 0 = flag, 1 = emoji, -1 = discard + +void main() { + // Decode instance ID → playerIdx + iconType (0=flag, 1=emoji) + int playerIdx = gl_InstanceID / 2; + int iconType = gl_InstanceID - playerIdx * 2; + + // Read player data + vec4 pd0 = texelFetch(uPlayerData, ivec2(0, playerIdx), 0); // srcX, srcY, srcScale, startTime + vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); // tgtX, tgtY, tgtScale, alive + vec4 pd3 = texelFetch(uPlayerData, ivec2(3, playerIdx), 0); // nameLen, troopLen, isHuman, nameHalfWidth + vec4 pd4 = texelFetch(uPlayerData, ivec2(4, playerIdx), 0); // flagIdx, emojiIdx, [free], [free] + + // Early out: dead player + if (pd1.w <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vIconType = -1; + return; + } + + // Get atlas index for this icon type + float atlasIdx = (iconType == 0) ? pd4.x : pd4.y; + if (atlasIdx < 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vIconType = -1; + return; + } + + // Lerped world position and size (same math as name.vert.glsl) + float elapsed = uTime - pd0.w; + float t = clamp(1.0 - exp(-uLerpSpeed * elapsed), 0.0, 1.0); + float wx = mix(pd0.x, pd1.x, t); + float wy = mix(pd0.y, pd1.y, t); + float ws = mix(pd0.z, pd1.z, t); + + // Sizing pipeline (must match name.vert.glsl exactly) + float baseSize = max(1.0, floor(ws)); + float nameSize = max(4.0, floor(baseSize * uNameScaleFactor)); + float nameScale = min(baseSize * 0.25, uNameScaleCap); + float nameWorldScale = (nameSize * nameScale) / uFontSize; + + // Zoom-based culling (same as name shader) + float cameraScale = length(vec2(uCamera[0][0], uCamera[1][0])); + float screenSize = nameWorldScale * uFontBase * cameraScale; + if (screenSize < uCullThreshold) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vIconType = -1; + return; + } + + float nameHalfWidth = pd3.w; // in font units (pre-scaled by nameWorldScale at runtime) + + // Compute icon size and position based on type + float iconW, iconH; + float cellW, cellH, cols, atlasW, atlasH; + vec2 iconOrigin; + + if (iconType == 0) { + // FLAG — to the left of the name + cellW = uFlagCellW; + cellH = uFlagCellH; + cols = uFlagCols; + atlasW = uFlagAtlasW; + atlasH = uFlagAtlasH; + + // Flag world size: height matches ~120% of the name text height + float flagWorldH = uFontBase * nameWorldScale * 1.2; + float flagWorldW = flagWorldH * (cellW / cellH); + + // Position: left of name, vertically centered on the name baseline + iconOrigin = vec2( + wx - nameHalfWidth * nameWorldScale - flagWorldW, + wy - flagWorldH * 0.5 + ); + iconW = flagWorldW; + iconH = flagWorldH; + } else { + // EMOJI — above the name + cellW = uEmojiCell; + cellH = uEmojiCell; + cols = uEmojiCols; + atlasW = uEmojiAtlasW; + atlasH = uEmojiAtlasH; + + // Emoji world size: slightly larger than name text height + float emojiWorldSize = uFontBase * nameWorldScale * 1.0; + + // Position: centered above name + iconOrigin = vec2( + wx - emojiWorldSize * 0.5, + wy - uFontBase * nameWorldScale * uEmojiRowOffset + ); + iconW = emojiWorldSize; + iconH = emojiWorldSize; + } + + // Quad world position + vec2 worldPos = iconOrigin + aPos * vec2(iconW, iconH); + + // Camera transform + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + // UV from atlas grid + int idx = int(atlasIdx); + int col = idx - (idx / int(cols)) * int(cols); + int row = idx / int(cols); + float u0 = float(col) * cellW / atlasW; + float v0 = float(row) * cellH / atlasH; + float u1 = u0 + cellW / atlasW; + float v1 = v0 + cellH / atlasH; + vUV = vec2(mix(u0, u1, aPos.x), mix(v0, v1, aPos.y)); + vIconType = iconType; +} diff --git a/src/client/render/gl/shaders/name/name.frag.glsl b/src/client/render/gl/shaders/name/name.frag.glsl new file mode 100644 index 0000000000..b2524f8b51 --- /dev/null +++ b/src/client/render/gl/shaders/name/name.frag.glsl @@ -0,0 +1,62 @@ +#version 300 es +precision highp float; + +uniform sampler2D uAtlas; +uniform float uDistRange; +uniform float uOutlineWidth; +uniform float uNightAmbient; +uniform vec3 uOutlineColor; +uniform float uOutlineUsePlayerColor; +uniform float uFillUsePlayerColor; + +in vec2 vUV; +in vec4 vPlayerColor; // player territory color (rgb) + alpha +in float vIsHuman; // 1.0 = human, 0.0 = bot/nation +out vec4 fragColor; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + // Degenerate fragment — skip + if (vPlayerColor.a <= 0.0) discard; + + // Stagger fill/border curves so they never share the same gray. + // t² for border (stays dark longer, snaps white late) and √t for fill (inverse). + // At midpoint t=0.5: border=0.25 (dark), fill=0.71 (light) — always distinct. + float t = 1.0 - uNightAmbient; + float borderT = t * t; + float fillT = sqrt(t); + + // Compute fill color: player color, or cycle-aware white↔black (inverse of border) + vec3 defaultFill = mix(uOutlineColor, vec3(0.0), fillT); + vec3 fillColor = mix(defaultFill, vPlayerColor.rgb, uFillUsePlayerColor); + + vec3 msd = texture(uAtlas, vUV).rgb; + float sd = median(msd.r, msd.g, msd.b); + + vec2 unitRange = uDistRange / vec2(textureSize(uAtlas, 0)); + vec2 screenTexSize = 1.0 / fwidth(vUV); + float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); + + float screenPxDist = screenPxRange * (sd - 0.5); + float fillAlpha = clamp(screenPxDist + 0.5, 0.0, 1.0); + + if (uOutlineWidth > 0.0) { + // The SDF saturates at sd=0 (screenPxDist = -screenPxRange*0.5). + // Reserve a 1px margin so saturated fragments always get alpha=0. + float maxOutline = max(screenPxRange * 0.5 - 1.0, 0.0); + float effectiveOutline = min(uOutlineWidth, maxOutline); + + float outlineDist = screenPxDist + effectiveOutline; + float outlineAlpha = clamp(outlineDist + 0.5, 0.0, 1.0); + + vec3 nightOutlineColor = mix(vec3(0.0), uOutlineColor, borderT); + vec3 borderColor = mix(nightOutlineColor, vPlayerColor.rgb, uOutlineUsePlayerColor); + vec3 color = mix(borderColor, fillColor, fillAlpha); + fragColor = vec4(color, vPlayerColor.a * outlineAlpha); + } else { + fragColor = vec4(fillColor, vPlayerColor.a * fillAlpha); + } +} diff --git a/src/client/render/gl/shaders/name/name.vert.glsl b/src/client/render/gl/shaders/name/name.vert.glsl new file mode 100644 index 0000000000..42b6c0c3d0 --- /dev/null +++ b/src/client/render/gl/shaders/name/name.vert.glsl @@ -0,0 +1,156 @@ +#version 300 es +precision highp float; +precision highp int; +precision highp usampler2D; + +// Unit quad vertex position [0,0]→[1,1] +layout(location = 0) in vec2 aPos; + +// Data textures +uniform sampler2D uGlyphMetrics; // CHAR_RANGE × 2, RGBA32F +uniform sampler2D uCursorX; // MAX_CHARS × (MAX_PLAYERS*2), R32F — pre-computed centered cursor X +uniform usampler2D uStrings; // MAX_CHARS × (MAX_PLAYERS*2), R8UI +uniform sampler2D uPlayerData; // 4 × MAX_PLAYERS, RGBA32F + +// Uniforms +uniform mat3 uCamera; +uniform float uTime; +uniform float uFontSize; // atlas reference font size +uniform float uAtlasScaleW; // atlas texture width +uniform float uAtlasScaleH; // atlas texture height +uniform float uBase; // atlas baseline height + +const int MAX_CHARS_PER_LINE = MAX_CHARS; +const int LINES = LINES_PER_PLAYER; +uniform float uLerpSpeed; +uniform float uCullThreshold; +uniform float uNameScaleFactor; +uniform float uNameScaleCap; +uniform float uTroopSizeMultiplier; + +out vec2 vUV; +out vec4 vPlayerColor; // player territory color (rgb) + alpha +out float vIsHuman; // 1.0 for human, 0.0 for bot/nation + +void main() { + // 1. Decode instance ID → playerIdx, lineIdx, charPos + int slotsPerPlayer = LINES * MAX_CHARS_PER_LINE; + int playerIdx = gl_InstanceID / slotsPerPlayer; + int remainder = gl_InstanceID - playerIdx * slotsPerPlayer; + int lineIdx = remainder / MAX_CHARS_PER_LINE; + int charPos = remainder - lineIdx * MAX_CHARS_PER_LINE; + + // 2. Read player data + vec4 pd0 = texelFetch(uPlayerData, ivec2(0, playerIdx), 0); // srcX, srcY, srcScale, startTime + vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); // tgtX, tgtY, tgtScale, alive + vec4 pd2 = texelFetch(uPlayerData, ivec2(2, playerIdx), 0); // r, g, b, a + vec4 pd3 = texelFetch(uPlayerData, ivec2(3, playerIdx), 0); // nameLen, troopLen, isHuman, 0 + + // Early out: dead player + if (pd1.w <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vPlayerColor = vec4(0.0); + vIsHuman = 0.0; + return; + } + + // String length for this line + int len = (lineIdx == 0) ? int(pd3.x) : int(pd3.y); + if (charPos >= len) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vPlayerColor = vec4(0.0); + vIsHuman = 0.0; + return; + } + + // 3. Read char code at this position + int stringRow = playerIdx * LINES + lineIdx; + uint charCode = texelFetch(uStrings, ivec2(charPos, stringRow), 0).r; + if (charCode == 0u) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vPlayerColor = vec4(0.0); + vIsHuman = 0.0; + return; + } + + // 4. Compute lerped world position and size + float elapsed = uTime - pd0.w; + float t = clamp(1.0 - exp(-uLerpSpeed * elapsed), 0.0, 1.0); + float wx = mix(pd0.x, pd1.x, t); + float wy = mix(pd0.y, pd1.y, t); + float ws = mix(pd0.z, pd1.z, t); + + // 5. Sizing pipeline (matches NameLayer.ts) + float baseSize = max(1.0, floor(ws)); + float nameSize = max(4.0, floor(baseSize * uNameScaleFactor)); + float nameScale = min(baseSize * 0.25, uNameScaleCap); + float nameWorldScale = (nameSize * nameScale) / uFontSize; + float worldScale = nameWorldScale; + + // Troop count is smaller + if (lineIdx == 1) { + worldScale *= uTroopSizeMultiplier; + } + + // Zoom-based culling: compute screen-space size and skip if too small + // uCamera[0][0] is the x-scale component of the camera matrix + float cameraScale = length(vec2(uCamera[0][0], uCamera[1][0])); + float screenSize = nameWorldScale * uBase * cameraScale; + if (screenSize < uCullThreshold) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vPlayerColor = vec4(0.0); + vIsHuman = 0.0; + return; + } + + // 6. Read pre-computed centered cursor X position + float cursorX = texelFetch(uCursorX, ivec2(charPos, stringRow), 0).r; + + // 7. Glyph metrics for this character + vec4 m0 = texelFetch(uGlyphMetrics, ivec2(int(charCode), 0), 0); // xadvance, xoffset, yoffset, width + vec4 m1 = texelFetch(uGlyphMetrics, ivec2(int(charCode), 1), 0); // height, atlasU0, atlasV0, atlasU1 + // atlasV1 packed: we need 5 values from 2 RGBA texels (8 slots), so atlasV1 is in m0 slot? + // Actually let's use: m0=(xadvance, xoffset, yoffset, width), m1=(height, u0, v0, u1), and compute v1 + float glyphW = m0.w; + float glyphH = m1.x; + float u0 = m1.y; + float v0 = m1.z; + float u1 = m1.w; + float v1 = v0 + glyphH / uAtlasScaleH; + + // Degenerate if glyph has no size (e.g. space) + if (glyphW <= 0.0 || glyphH <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vPlayerColor = vec4(0.0); + vIsHuman = 0.0; + return; + } + + // 8. Compute world-space quad position + float baselineY = -uBase * 0.5; // center vertically + // Use name-line scale for offset so troops sit below the name, not overlapping + float lineOffsetY = (lineIdx == 1) ? uBase * nameWorldScale * 1.1 : 0.0; + + vec2 glyphOrigin = vec2( + cursorX + m0.y, // + xoffset + baselineY + m0.z // + yoffset + ) * worldScale; + + vec2 glyphSize = vec2(glyphW, glyphH) * worldScale; + + vec2 worldPos = vec2(wx, wy + lineOffsetY) + glyphOrigin + aPos * glyphSize; + + // 9. Camera transform + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + // 10. UV interpolation across quad + vUV = vec2(mix(u0, u1, aPos.x), mix(v0, v1, aPos.y)); + vPlayerColor = pd2; // player territory color (rgb) + alpha + vIsHuman = pd3.z; // 1.0 = human, 0.0 = bot/nation +} diff --git a/src/client/render/gl/shaders/name/status-icon.frag.glsl b/src/client/render/gl/shaders/name/status-icon.frag.glsl new file mode 100644 index 0000000000..e8ac082c03 --- /dev/null +++ b/src/client/render/gl/shaders/name/status-icon.frag.glsl @@ -0,0 +1,40 @@ +#version 300 es +precision highp float; + +uniform sampler2D uStatusAtlas; + +in vec2 vUV; +in vec2 vLocalUV; +flat in int vDiscard; +flat in float vAllianceFraction; +flat in vec2 vFadedUV0; +flat in vec2 vFadedUV1; +flat in float vFlashAlpha; + +out vec4 fragColor; + +void main() { + if (vDiscard != 0) discard; + + vec4 texel = texture(uStatusAtlas, vUV); + + // Alliance drain: composite faded icon behind colored icon, clipped by fraction. + // Matches the game's CSS clip-path: inset(topCut% -2px 0 -2px) behavior. + if (vAllianceFraction > 0.0) { + // Game formula: topCut = 20 + (1-fraction) * 80 * 0.78 (% → 0..1) + float topCut = 0.20 + (1.0 - vAllianceFraction) * 0.624; + + // Sample faded icon at corresponding local position + vec2 fadedUV = mix(vFadedUV0, vFadedUV1, vLocalUV); + vec4 fadedTexel = texture(uStatusAtlas, fadedUV); + + // Above the cut line → show faded; below → show colored + texel = vLocalUV.y < topCut ? fadedTexel : texel; + } + + // Traitor flash: modulate alpha for urgency pulse + texel.a *= vFlashAlpha; + + if (texel.a < 0.01) discard; + fragColor = texel; +} diff --git a/src/client/render/gl/shaders/name/status-icon.vert.glsl b/src/client/render/gl/shaders/name/status-icon.vert.glsl new file mode 100644 index 0000000000..dd5c6af27e --- /dev/null +++ b/src/client/render/gl/shaders/name/status-icon.vert.glsl @@ -0,0 +1,217 @@ +#version 300 es +precision highp float; +precision highp int; + +// Unit quad vertex position [0,0]→[1,1] +layout(location = 0) in vec2 aPos; + +// Data textures (shared with name shader) +uniform sampler2D uPlayerData; // 8 × MAX_PLAYERS, RGBA32F + +// Uniforms +uniform mat3 uCamera; +uniform float uTime; +uniform float uLerpSpeed; +uniform float uCullThreshold; +uniform float uNameScaleFactor; +uniform float uNameScaleCap; +uniform float uFontSize; +uniform float uFontBase; + +// Status atlas layout +uniform float uStatusCell; // texels per cell (square) +uniform float uStatusCols; // columns in atlas +uniform float uStatusAtlasW; // atlas texture width +uniform float uStatusAtlasH; // atlas texture height +uniform float uStatusPad; // transparent padding in texels per side + +// Configurable layout +uniform float uStatusRowOffset; // row Y offset (multiples of uFontBase * nameWorldScale) + +out vec2 vUV; +out vec2 vLocalUV; // 0..1 within the icon cell +flat out int vDiscard; +flat out float vAllianceFraction; // 0 = no drain effect, >0 = active drain +flat out vec2 vFadedUV0; // top-left UV of faded alliance cell +flat out vec2 vFadedUV1; // bottom-right UV of faded alliance cell +flat out float vFlashAlpha; // traitor flash opacity (1.0 = fully visible) + +// Status flag float array — indexed by icon slot. +// Slot mapping: 0=crown, 1=traitor, 2=disconnected, 3=alliance, +// 4=allianceReq, 5=target, 6=embargo, 7=nukeActive +float statusFlag[8]; + +// Read status flags from pd5/pd6 into the statusFlag array. +void readStatusFlags(int playerIdx) { + vec4 pd5 = texelFetch(uPlayerData, ivec2(5, playerIdx), 0); + vec4 pd6 = texelFetch(uPlayerData, ivec2(6, playerIdx), 0); + statusFlag[0] = pd5.x; // crown + statusFlag[1] = pd5.y; // traitor + statusFlag[2] = pd5.z; // disconnected + statusFlag[3] = pd5.w; // alliance + statusFlag[4] = pd6.x; // allianceReq + statusFlag[5] = pd6.y; // target + statusFlag[6] = pd6.z; // embargo + statusFlag[7] = pd6.w; // nukeActive +} + +// Count active icons with index < pos. +int countBelow(int pos) { + int count = 0; + for (int i = 0; i < pos; i++) { + if (statusFlag[i] > 0.5) count++; + } + return count; +} + +// Compute padded UV rect for an atlas cell. +// Returns (u0, v0) in xy and (u1, v1) in zw, inset by pad pixels. +vec4 cellUV(int idx) { + int col = idx - (idx / int(uStatusCols)) * int(uStatusCols); + int row = idx / int(uStatusCols); + float u0 = (float(col) * uStatusCell + uStatusPad) / uStatusAtlasW; + float v0 = (float(row) * uStatusCell + uStatusPad) / uStatusAtlasH; + float iconSize = uStatusCell - 2.0 * uStatusPad; + float u1 = u0 + iconSize / uStatusAtlasW; + float v1 = v0 + iconSize / uStatusAtlasH; + return vec4(u0, v0, u1, v1); +} + +void main() { + // Decode instance ID → playerIdx + iconSlot (0..7) + int playerIdx = gl_InstanceID / 8; + int iconSlot = gl_InstanceID - playerIdx * 8; + + // Read player data + vec4 pd0 = texelFetch(uPlayerData, ivec2(0, playerIdx), 0); // srcX, srcY, srcScale, startTime + vec4 pd1 = texelFetch(uPlayerData, ivec2(1, playerIdx), 0); // tgtX, tgtY, tgtScale, alive + vec4 pd7 = texelFetch(uPlayerData, ivec2(7, playerIdx), 0); // nukeTargetsMe, traitorRemainingTicks, allianceFraction, [free] + + // Early out: dead player + if (pd1.w <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vLocalUV = vec2(0.0); + vDiscard = 1; + vAllianceFraction = 0.0; + vFadedUV0 = vec2(0.0); + vFadedUV1 = vec2(0.0); + vFlashAlpha = 1.0; + return; + } + + // Read status flags into array + readStatusFlags(playerIdx); + + // Early out: this icon slot is inactive + if (statusFlag[iconSlot] < 0.5) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vLocalUV = vec2(0.0); + vDiscard = 1; + vAllianceFraction = 0.0; + vFadedUV0 = vec2(0.0); + vFadedUV1 = vec2(0.0); + vFlashAlpha = 1.0; + return; + } + + // Lerped world position and size (same math as name.vert.glsl) + float elapsed = uTime - pd0.w; + float t = clamp(1.0 - exp(-uLerpSpeed * elapsed), 0.0, 1.0); + float wx = mix(pd0.x, pd1.x, t); + float wy = mix(pd0.y, pd1.y, t); + float ws = mix(pd0.z, pd1.z, t); + + // Sizing pipeline (must match name.vert.glsl exactly) + float baseSize = max(1.0, floor(ws)); + float nameSize = max(4.0, floor(baseSize * uNameScaleFactor)); + float nameScale = min(baseSize * 0.25, uNameScaleCap); + float nameWorldScale = (nameSize * nameScale) / uFontSize; + + // Zoom-based culling (same as name shader) + float cameraScale = length(vec2(uCamera[0][0], uCamera[1][0])); + float screenSize = nameWorldScale * uFontBase * cameraScale; + if (screenSize < uCullThreshold) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vLocalUV = vec2(0.0); + vDiscard = 1; + vAllianceFraction = 0.0; + vFadedUV0 = vec2(0.0); + vFadedUV1 = vec2(0.0); + vFlashAlpha = 1.0; + return; + } + + // Icon world size: matches name text height + float iconWorldSize = uFontBase * nameWorldScale * 1.1; + + // Count active icons and position of this one (left-to-right) + int totalActive = 0; + for (int i = 0; i < 8; i++) { + if (statusFlag[i] > 0.5) totalActive++; + } + int myIndex = countBelow(iconSlot); + + // Horizontal centering: spread icons evenly above the name + float gap = iconWorldSize * 0.15; + float totalWidth = float(totalActive) * iconWorldSize + float(totalActive - 1) * gap; + float startX = wx - totalWidth * 0.5; + float iconX = startX + float(myIndex) * (iconWorldSize + gap); + + // Position: row above the emoji row + float iconY = wy - uFontBase * nameWorldScale * uStatusRowOffset; + + // Determine atlas index + // Slots 0-6 map directly to atlas indices 0-6 + // Slot 7 (nuke): use nukeRed (7) if nukeTargetsMe, else nukeWhite (8) + int atlasIdx = iconSlot; + if (iconSlot == 7) { + atlasIdx = (pd7.x > 0.5) ? 7 : 8; + } + + // Quad world position + vec2 iconOrigin = vec2(iconX, iconY); + vec2 worldPos = iconOrigin + aPos * vec2(iconWorldSize, iconWorldSize); + + // Camera transform + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + // UV from atlas grid (padded to avoid mipmap bleed) + vec4 uv = cellUV(atlasIdx); + vUV = vec2(mix(uv.x, uv.z, aPos.x), mix(uv.y, uv.w, aPos.y)); + vLocalUV = aPos; + + // Alliance drain: slot 3 = alliance icon + float allianceFrac = pd7.z; + if (iconSlot == 3 && allianceFrac > 0.0 && allianceFrac < 1.0) { + vAllianceFraction = allianceFrac; + // Faded alliance icon is at atlas index 9 + vec4 fadedUV = cellUV(9); + vFadedUV0 = fadedUV.xy; + vFadedUV1 = fadedUV.zw; + } else { + vAllianceFraction = 0.0; + vFadedUV0 = vec2(0.0); + vFadedUV1 = vec2(0.0); + } + + // Traitor flash: slot 1 = traitor icon + // Frequency ramps linearly from 2 Hz (at 15s) to 5 Hz (at 0s). + // Phase = uTime*2 + elapsed²*0.1 — the quadratic term adds smooth + // acceleration without phase discontinuities between ticks. + vFlashAlpha = 1.0; + if (iconSlot == 1) { + float remaining = pd7.y; // ticks (0-300, 10/sec) + float remainingSec = remaining / 10.0; // seconds + if (remainingSec <= 15.0 && remainingSec > 0.0) { + float elapsed = 15.0 - remainingSec; + float phase = uTime * 2.0 + elapsed * elapsed * 0.1; + vFlashAlpha = 0.3 + 0.7 * (0.5 + 0.5 * cos(phase * 6.2832)); + } + } + + vDiscard = 0; +} diff --git a/src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.frag.glsl b/src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.frag.glsl new file mode 100644 index 0000000000..dbc2081f79 --- /dev/null +++ b/src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.frag.glsl @@ -0,0 +1,56 @@ +#version 300 es +precision highp float; + +in vec2 vLocal; +flat in float vInnerRadius; +flat in float vOuterRadius; + +uniform float uTime; // seconds +uniform vec4 uTelegraphStyle; // (strokeWidth, dashLen, gapLen, rotationSpeed) +uniform vec4 uTelegraphAlpha; // (baseAlpha, pulseAmplitude, pulseSpeed, fillAlphaOffset) +uniform vec3 uTelegraphColor; + +out vec4 fragColor; + +void main() { + float strokeWidth = uTelegraphStyle.x; + float dashLen = uTelegraphStyle.y; + float gapLen = uTelegraphStyle.z; + float rotationSpeed = uTelegraphStyle.w; + float baseAlphaVal = uTelegraphAlpha.x; + float pulseAmp = uTelegraphAlpha.y; + float pulseSpd = uTelegraphAlpha.z; + float fillAlphaOff = uTelegraphAlpha.w; + + float paddedR = vOuterRadius + 2.0; + float dist = length(vLocal) * paddedR; + + // Base alpha with gentle pulsation + float baseAlpha = baseAlphaVal + pulseAmp * sin(uTime * pulseSpd); + + // Inner circle: filled disc + stroke + float innerFill = 1.0 - smoothstep(vInnerRadius - 0.5, vInnerRadius, dist); + float innerStroke = smoothstep(vInnerRadius - strokeWidth - 0.5, vInnerRadius - strokeWidth, dist) + * (1.0 - smoothstep(vInnerRadius + strokeWidth, vInnerRadius + strokeWidth + 0.5, dist)); + + // Outer circle: dashed ring + float outerRing = smoothstep(vOuterRadius - strokeWidth - 0.5, vOuterRadius - strokeWidth, dist) + * (1.0 - smoothstep(vOuterRadius + strokeWidth, vOuterRadius + strokeWidth + 0.5, dist)); + + // Dash pattern on outer ring + float angle = atan(vLocal.y, vLocal.x); + float arcPos = angle * vOuterRadius; + float period = dashLen + gapLen; + float dashPhase = mod(arcPos + uTime * rotationSpeed, period); + float dashAlpha = 1.0 - smoothstep(dashLen - 0.5, dashLen + 0.5, dashPhase); + + // Combine + float fillAlpha = innerFill * max(0.0, baseAlpha - fillAlphaOff); + float strokeAlpha = innerStroke * baseAlpha; + float outerAlpha = outerRing * dashAlpha * baseAlpha; + + float alpha = max(max(fillAlpha, strokeAlpha), outerAlpha); + if (alpha < 0.01) discard; + + fragColor = vec4(uTelegraphColor, alpha); +} diff --git a/src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.vert.glsl b/src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.vert.glsl new file mode 100644 index 0000000000..a53be58d0c --- /dev/null +++ b/src/client/render/gl/shaders/nuke-telegraph/nuke-telegraph.vert.glsl @@ -0,0 +1,27 @@ +#version 300 es +precision highp float; + +// Unit quad [0,1] +layout(location = 0) in vec2 aPos; +// Per-instance: x, y, innerRadius, outerRadius +layout(location = 1) in vec4 aInstance; + +uniform mat3 uCamera; + +out vec2 vLocal; // [-1, +1] local coords +flat out float vInnerRadius; +flat out float vOuterRadius; + +void main() { + vLocal = aPos * 2.0 - 1.0; + vInnerRadius = aInstance.z; + vOuterRadius = aInstance.w; + + // Expand quad to cover outer circle bbox + padding + float r = aInstance.w + 2.0; + vec2 center = aInstance.xy + 0.5; + vec2 worldPos = center + vLocal * r; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.frag.glsl b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.frag.glsl new file mode 100644 index 0000000000..62ce075694 --- /dev/null +++ b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.frag.glsl @@ -0,0 +1,45 @@ +#version 300 es +precision highp float; + +in vec2 vUV; +flat in float vType; + +out vec4 fragColor; + +// Colors matching upstream +const vec3 COLOR_WHITE = vec3(1.0); +const vec3 OUTLINE_GRAY = vec3(0.549); // rgba(140, 140, 140) +const vec3 COLOR_RED = vec3(1.0, 0.0, 0.0); +const vec3 OUTLINE_BLACK = vec3(0.0); + +void main() { + float alpha = 0.0; + vec3 color = vec3(1.0); + + if (vType < 0.5) { + // Circle marker at untargetable zone boundary + // White ring with gray outline (upstream: 4px radius, 1.25px stroke) + float dist = length(vUV); + float ring = abs(dist - 0.55); + float lineAlpha = 1.0 - smoothstep(0.06, 0.12, ring); + float outlineAlpha = 1.0 - smoothstep(0.14, 0.22, ring); + float blend = outlineAlpha > 0.01 ? lineAlpha / outlineAlpha : 1.0; + color = mix(OUTLINE_GRAY, COLOR_WHITE, blend); + alpha = outlineAlpha; + } else { + // X marker at SAM intercept point + // Red X with black outline (upstream: 6px arms, 2px stroke) + float d1 = abs(vUV.x - vUV.y) * 0.7071; + float d2 = abs(vUV.x + vUV.y) * 0.7071; + float minD = min(d1, d2); + float circleMask = 1.0 - smoothstep(0.7, 0.85, length(vUV)); + float lineAlpha = (1.0 - smoothstep(0.08, 0.16, minD)) * circleMask; + float outlineAlpha = (1.0 - smoothstep(0.18, 0.28, minD)) * circleMask; + float blend = outlineAlpha > 0.01 ? lineAlpha / outlineAlpha : 1.0; + color = mix(OUTLINE_BLACK, COLOR_RED, blend); + alpha = outlineAlpha; + } + + if (alpha < 0.01) discard; + fragColor = vec4(color, alpha); +} diff --git a/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.vert.glsl b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.vert.glsl new file mode 100644 index 0000000000..4f75479cec --- /dev/null +++ b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory-marker.vert.glsl @@ -0,0 +1,33 @@ +#version 300 es +precision highp float; + +// Unit quad [-1, +1] +layout(location = 0) in vec2 aCorner; + +uniform mat3 uCamera; +uniform vec2 uP0, uP1, uP2, uP3; +uniform float uPixelSize; +uniform vec4 uMarker; // (t, type: 0=circle 1=X, 0, 0) +uniform vec2 uMarkerRadii; // (circleRadiusPx, xRadiusPx) + +out vec2 vUV; +flat out float vType; + +vec2 bezier(float t) { + float T = 1.0 - t; + float TT = T * T; + float tt = t * t; + return TT * T * uP0 + 3.0 * TT * t * uP1 + 3.0 * T * tt * uP2 + tt * t * uP3; +} + +void main() { + vType = uMarker.y; + vUV = aCorner; + + vec2 center = bezier(uMarker.x) + 0.5; + float radius = (vType < 0.5 ? uMarkerRadii.x : uMarkerRadii.y) * uPixelSize; + vec2 pos = center + aCorner * radius; + + vec3 clip = uCamera * vec3(pos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.frag.glsl b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.frag.glsl new file mode 100644 index 0000000000..f291e702ba --- /dev/null +++ b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.frag.glsl @@ -0,0 +1,54 @@ +#version 300 es +precision highp float; + +in float vT; +in float vArcDist; +in float vEdgeDist; + +uniform float uPixelSize; +uniform float uTUntargetableStart; // -1 = no zone +uniform float uTUntargetableEnd; // -1 = no zone +uniform float uTSamIntercept; // 1.0 = no intercept + +// Settings uniforms +uniform float uQuadHalfPx; // total half-width of quad in pixels +uniform float uLineHalfPx; // main line half-width in pixels +uniform float uOutlineHalfPx; // outline half-width in pixels +uniform vec4 uDashPattern; // (dashTargetable, gapTargetable, dashUntargetable, gapUntargetable) +uniform vec3 uLineColor; // normal line color +uniform vec3 uInterceptColor; // line color after SAM intercept +uniform vec3 uOutlineColor; // outline color (normal) +uniform vec3 uInterceptOutlineColor; // outline color (after intercept) + +out vec4 fragColor; + +void main() { + // Zone classification + bool inUntargetable = uTUntargetableStart >= 0.0 + && vT >= uTUntargetableStart + && vT <= uTUntargetableEnd; + bool intercepted = vT >= uTSamIntercept; + + // Dash pattern (pixel space) + float dashLen = inUntargetable ? uDashPattern.z : uDashPattern.x; + float gapLen = inUntargetable ? uDashPattern.w : uDashPattern.y; + float period = dashLen + gapLen; + float pixelDist = vArcDist / uPixelSize; + float phase = mod(pixelDist, period); + float dashAlpha = 1.0 - smoothstep(dashLen - 0.5, dashLen + 0.5, phase); + if (dashAlpha < 0.01) discard; + + // Line vs outline (pixel distance from center line) + float d = abs(vEdgeDist) * uQuadHalfPx; + float lineAlpha = 1.0 - smoothstep(uLineHalfPx - 0.4, uLineHalfPx + 0.4, d); + float outlineAlpha = 1.0 - smoothstep(uOutlineHalfPx - 0.4, uOutlineHalfPx + 0.4, d); + if (outlineAlpha < 0.01) discard; + + // Color selection + vec3 lineColor = intercepted ? uInterceptColor : uLineColor; + vec3 outlineColor = intercepted ? uInterceptOutlineColor : uOutlineColor; + float blend = outlineAlpha > 0.01 ? lineAlpha / outlineAlpha : 1.0; + vec3 color = mix(outlineColor, lineColor, blend); + + fragColor = vec4(color, outlineAlpha * dashAlpha); +} diff --git a/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.vert.glsl b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.vert.glsl new file mode 100644 index 0000000000..fd7a4d4bef --- /dev/null +++ b/src/client/render/gl/shaders/nuke-trajectory/nuke-trajectory.vert.glsl @@ -0,0 +1,48 @@ +#version 300 es +precision highp float; + +// Per-vertex: (t along curve, side: -1 or +1, cumulative arc distance) +layout(location = 0) in vec3 aVertex; + +uniform mat3 uCamera; +uniform vec2 uP0, uP1, uP2, uP3; // Bezier control points +uniform float uPixelSize; // world units per pixel +uniform float uQuadHalfPx; // half-width of quad in pixels + +out float vT; // curve parameter (0..1) +out float vArcDist; // cumulative arc distance (world units) +out float vEdgeDist; // -1..+1 across the line width + +vec2 bezier(float t) { + float T = 1.0 - t; + float TT = T * T; + float tt = t * t; + return TT * T * uP0 + 3.0 * TT * t * uP1 + 3.0 * T * tt * uP2 + tt * t * uP3; +} + +vec2 bezierDeriv(float t) { + float T = 1.0 - t; + return 3.0 * (T * T * (uP1 - uP0) + 2.0 * T * t * (uP2 - uP1) + t * t * (uP3 - uP2)); +} + +void main() { + float t = aVertex.x; + float side = aVertex.y; + + vec2 pos = bezier(t); + vec2 tang = bezierDeriv(t); + float tangLen = length(tang); + vec2 normTang = tangLen > 0.001 ? tang / tangLen : vec2(1.0, 0.0); + vec2 perp = vec2(-normTang.y, normTang.x); + + float halfWidth = uQuadHalfPx * uPixelSize; + pos += perp * side * halfWidth; + pos += 0.5; // tile center offset + + vT = t; + vEdgeDist = side; + vArcDist = aVertex.z; + + vec3 clip = uCamera * vec3(pos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/radial-menu/arcs.frag.glsl b/src/client/render/gl/shaders/radial-menu/arcs.frag.glsl new file mode 100644 index 0000000000..12481b223d --- /dev/null +++ b/src/client/render/gl/shaders/radial-menu/arcs.frag.glsl @@ -0,0 +1,132 @@ +#version 300 es +precision highp float; + +in vec2 vLocal; // [-1, +1], distance 1.0 = outerR + +uniform float uInnerR; // inner radius as fraction of outerR [0,1] +uniform int uSegCount; // number of segments (1..8) +uniform int uHoveredSeg; // hovered segment index (-1 = none) +uniform vec4 uSegColors[8]; // per-segment: rgb + enabled (a: 1 = enabled, 0 = disabled) + +// Center button +uniform int uHasCenterBtn; // 1 = show center button +uniform vec3 uCenterColor; // center button RGB +uniform int uCenterHovered; // 1 = center button hovered + +out vec4 fragColor; + +const float GAP = 0.03; // radians gap between segments (game: padAngle 0.03) +const float AA = 0.010; // anti-alias width (normalized coords) +const float BORDER_W = 0.024; // border width, non-hovered +const float BORDER_W_HOV = 0.034; // border width, hovered (thicker) +const float PI = 3.14159265359; +const float TWO_PI = 6.28318530718; + +void main() { + float dist = length(vLocal); + + // --- Center button zone --- + if (dist < uInnerR - AA) { + if (uHasCenterBtn == 0) discard; + + // Solid center fill — fade alpha only at outer edge + float centerAlpha = 1.0 - smoothstep(uInnerR - AA * 3.0, uInnerR - AA, dist); + + bool cHov = uCenterHovered > 0; + float cbw = cHov ? BORDER_W_HOV : BORDER_W; + vec3 cbCol = cHov ? vec3(1.0) : vec3(0.88); + + // Crisp border at outer edge of center circle + float borderDist = uInnerR - AA - dist; + float border = 1.0 - smoothstep(cbw - AA, cbw + AA, borderDist); + + vec3 color = uCenterColor; + if (cHov) color = mix(color, vec3(1.0), 0.2); + color = mix(color, cbCol, border); + + float cAlpha = cHov ? 0.92 : 0.6; + fragColor = vec4(color, cAlpha * centerAlpha); + return; + } + + // --- Ring zone --- + if (uSegCount == 0) discard; // center-only mode + + // Annulus mask + float outer = 1.0 - smoothstep(1.0 - AA, 1.0, dist); + float inner = smoothstep(uInnerR - AA, uInnerR + AA, dist); + float ring = outer * inner; + if (ring < 0.01) discard; + + // Angle: 0 at top, increasing clockwise [0, 2π] + float angle = atan(vLocal.x, -vLocal.y); + if (angle < 0.0) angle += TWO_PI; + + // Rotate so first segment is centered at top (game: startAngle = -π/n) + float segArc = TWO_PI / float(uSegCount); + float offset = PI / float(uSegCount); + float shifted = mod(angle + offset, TWO_PI); + + // Segment index (in rotated space) + int segIdx = int(floor(shifted / segArc)); + segIdx = min(segIdx, uSegCount - 1); + + // Gap mask between segments + float segStart = float(segIdx) * segArc; + float segEnd = segStart + segArc; + float halfGap = GAP * 0.5; + + float gap = 1.0; + if (uSegCount > 1) { + gap = smoothstep(segStart + halfGap - AA, segStart + halfGap + AA, shifted) + * (1.0 - smoothstep(segEnd - halfGap - AA, segEnd - halfGap + AA, shifted)); + } + + float alpha = ring * gap; + if (alpha < 0.01) discard; + + // Segment color + hover state + vec4 seg = uSegColors[segIdx]; + vec3 color = seg.rgb; + bool enabled = seg.a > 0.5; + bool hovered = (segIdx == uHoveredSeg && enabled); + + // Pick border width & color based on hover + float bw = hovered ? BORDER_W_HOV : BORDER_W; + vec3 borderCol = hovered ? vec3(1.0) : vec3(0.88); + + // --- Borders --- + // Outer edge + float outerBorder = 1.0 - smoothstep(bw - AA, bw + AA, 1.0 - dist); + // Inner edge + float innerBorder = 1.0 - smoothstep(bw - AA, bw + AA, dist - uInnerR); + // Radial lines at gap edges + float angBorder = 0.0; + if (uSegCount > 1) { + float angleInSeg = shifted - segStart; + float distToStart = angleInSeg - halfGap; + float distToEnd = (segArc - halfGap) - angleInSeg; + // Convert angular distance to approximate normalized arc-length + float nearestAng = min(distToStart, distToEnd) * dist; + angBorder = 1.0 - smoothstep(bw - AA, bw + AA, nearestAng); + } + float border = max(max(outerBorder, innerBorder), angBorder); + + // Disabled segments: desaturate + darken + if (!enabled) { + float lum = dot(color, vec3(0.3, 0.6, 0.1)); + color = vec3(lum) * 0.4; + } + + // Hover highlight: brighten fill + if (hovered) { + color = mix(color, vec3(1.0), 0.2); + } + + // Blend border on top + color = mix(color, borderCol, border); + + // Opacity: hovered → nearly opaque, default → slightly transparent, disabled → dim + float segAlpha = enabled ? (hovered ? 0.92 : 0.6) : 0.4; + fragColor = vec4(color, alpha * segAlpha); +} diff --git a/src/client/render/gl/shaders/radial-menu/arcs.vert.glsl b/src/client/render/gl/shaders/radial-menu/arcs.vert.glsl new file mode 100644 index 0000000000..5e5b379abe --- /dev/null +++ b/src/client/render/gl/shaders/radial-menu/arcs.vert.glsl @@ -0,0 +1,24 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; // [0,1] quad + +uniform vec2 uAnchor; // anchor in device pixels +uniform float uOuterR; // outer radius in device pixels +uniform vec2 uViewport; // drawingBuffer width, height + +out vec2 vLocal; // [-1, +1] square pixel-space + +void main() { + vLocal = aPos * 2.0 - 1.0; + + // Expand quad to [-outerR, +outerR] in device pixels around anchor + vec2 pos = uAnchor + vLocal * uOuterR; + + // Device pixels → NDC + gl_Position = vec4( + pos.x / uViewport.x * 2.0 - 1.0, + 1.0 - pos.y / uViewport.y * 2.0, + 0.0, 1.0 + ); +} diff --git a/src/client/render/gl/shaders/radial-menu/icon.frag.glsl b/src/client/render/gl/shaders/radial-menu/icon.frag.glsl new file mode 100644 index 0000000000..79862e014c --- /dev/null +++ b/src/client/render/gl/shaders/radial-menu/icon.frag.glsl @@ -0,0 +1,27 @@ +#version 300 es +precision highp float; + +in vec2 vUV; +flat in float vAtlasIdx; +flat in float vOpacity; + +uniform sampler2D uEmojiAtlas; +uniform float uEmojiCell; +uniform float uEmojiCols; +uniform float uEmojiAtlasW; +uniform float uEmojiAtlasH; + +out vec4 fragColor; + +void main() { + if (vAtlasIdx < 0.0) discard; + + float col = mod(vAtlasIdx, uEmojiCols); + float row = floor(vAtlasIdx / uEmojiCols); + + vec2 cellOrigin = vec2(col * uEmojiCell / uEmojiAtlasW, row * uEmojiCell / uEmojiAtlasH); + vec2 cellSize = vec2(uEmojiCell / uEmojiAtlasW, uEmojiCell / uEmojiAtlasH); + + vec4 texel = texture(uEmojiAtlas, cellOrigin + vUV * cellSize); + fragColor = vec4(texel.rgb, texel.a * vOpacity); +} diff --git a/src/client/render/gl/shaders/radial-menu/icon.vert.glsl b/src/client/render/gl/shaders/radial-menu/icon.vert.glsl new file mode 100644 index 0000000000..ee6fc4899e --- /dev/null +++ b/src/client/render/gl/shaders/radial-menu/icon.vert.glsl @@ -0,0 +1,74 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; // [0,1] quad + +uniform vec2 uAnchor; // anchor in device pixels +uniform float uOuterR; // outer radius in device pixels +uniform float uInnerR; // inner radius as fraction of outerR [0,1] +uniform vec2 uViewport; // drawingBuffer width, height +uniform int uSegCount; // number of segments +uniform float uIconHalf; // icon half-size in device pixels +uniform float uEmojiIndices[8]; // atlas index per segment (-1 = none) +uniform float uCenterEmojiIdx; // atlas index for center icon (-1 = none) +uniform float uSegOpacity[8]; // per-segment opacity (0..1) + +out vec2 vUV; +flat out float vAtlasIdx; +flat out float vOpacity; + +const float PI = 3.14159265359; +const float TWO_PI = 6.28318530718; + +void main() { + int segIdx = gl_InstanceID; + + // Center icon: last instance (index == uSegCount) + if (segIdx == uSegCount) { + vAtlasIdx = uCenterEmojiIdx; + vOpacity = 1.0; // center icon always full opacity + if (vAtlasIdx < 0.0) { + gl_Position = vec4(2.0, 2.0, 0.0, 1.0); + vUV = vec2(0.0); + return; + } + // Position at anchor center — always upright + vec2 local = aPos * 2.0 - 1.0; + vec2 pos = uAnchor + local * uIconHalf; + gl_Position = vec4( + pos.x / uViewport.x * 2.0 - 1.0, + 1.0 - pos.y / uViewport.y * 2.0, + 0.0, 1.0 + ); + vUV = aPos; + return; + } + + vAtlasIdx = uEmojiIndices[segIdx]; + vOpacity = uSegOpacity[segIdx]; + + if (vAtlasIdx < 0.0 || segIdx >= uSegCount) { + gl_Position = vec4(2.0, 2.0, 0.0, 1.0); + vUV = vec2(0.0); + return; + } + + // Arc center position — rotated so first segment is centered at top + float segArc = TWO_PI / float(uSegCount); + float offset = PI / float(uSegCount); + float angle = (float(segIdx) + 0.5) * segArc - offset; + float midR = (uInnerR + 1.0) * 0.5 * uOuterR; + vec2 center = uAnchor + vec2(sin(angle), -cos(angle)) * midR; + + // Quad corners — always axis-aligned (upright icons) + vec2 local = aPos * 2.0 - 1.0; + vec2 pos = center + local * uIconHalf; + + gl_Position = vec4( + pos.x / uViewport.x * 2.0 - 1.0, + 1.0 - pos.y / uViewport.y * 2.0, + 0.0, 1.0 + ); + + vUV = aPos; +} diff --git a/src/client/render/gl/shaders/railroad/railroad.frag.glsl b/src/client/render/gl/shaders/railroad/railroad.frag.glsl new file mode 100644 index 0000000000..0b363625d3 --- /dev/null +++ b/src/client/render/gl/shaders/railroad/railroad.frag.glsl @@ -0,0 +1,155 @@ +#version 300 es +precision highp float; +precision highp usampler2D; + +uniform usampler2D uRailroadTex; // R8UI — rail type per tile (0=none, 1-6) +uniform usampler2D uGhostRailTex; // R8UI — ghost rail type per tile (0=none, 1-6) +uniform usampler2D uTileTex; // R16UI — tile state (for owner lookup) +uniform sampler2D uPalette; // RGBA32F — player colors +uniform usampler2D uTerrainTex; // R8UI — terrain bytes (bit 7 = isLand) + +uniform vec2 uMapSize; +uniform float uZoom; +uniform float uRailDetailZoom; +uniform float uRailAlpha; +uniform float uGhostOwnerID; // Player smallID for ghost rail color + +in vec2 vWorldPos; +out vec4 fragColor; + +// Bridge pixel positions per rail type, from OpenFrontIO's RailroadSprites.ts. +// Tests whether 2x-pixel offset (lp) from a tile origin is a bridge pixel. +// Bridge pixel positions from game's RailroadSprites.ts, with -2 offsets +// shifted to -1 to close the gap (game's rail extends into neighbors, ours doesn't). +bool isBridgePixel(uint rt, ivec2 lp) { + int x = lp.x, y = lp.y; + if (rt == 1u) { // Vertical + return (x == -1 || x == 2) && y >= -1 && y <= 1; + } else if (rt == 2u) { // Horizontal + return (y == -1 && x >= -1 && x <= 1) + || (y == 2 && x >= -1 && x <= 1) + || (y == 3 && (x == -1 || x == 1)); + } else if (rt == 3u) { // TopLeft + return (x == -1 && (y == -1 || y == 2)) + || (x == 0 && y == 1) + || (x == 1 && y == 0) + || (x == 2 && y == -1); + } else if (rt == 4u) { // TopRight + return (x == -1 && (y == -1 || y == 0)) + || (x == 0 && y == 1) + || (x == 1 && y == 2) + || (x == 2 && (y == -1 || y == 2)); + } else if (rt == 5u) { // BottomLeft + return (x == -1 && (y == -1 || y == 2)) + || (x == 0 && y == -1) + || (x == 1 && y == 0) + || (x == 2 && (y == 1 || y == 2)); + } else if (rt == 6u) { // BottomRight + return (x == -1 && y >= 0 && y <= 2) + || (x == 0 && y == -1) + || (x == 1 && y == -1) + || (x == 2 && (y == -1 || y == 2)); + } + return false; +} + +// Compute rail pixel coverage for a given rail type at fractional tile position. +// Returns 0.0 for miss, 1.0 for hit (detailed mode), or AA coverage (line mode). +float railCoverage(uint rt, vec2 f) { + if (rt == 0u) return 0.0; + + if (uZoom >= uRailDetailZoom) { + // Detailed mode: 3x3 sub-grid with cross-ties + float T = 1.0 / 3.0; + float T2 = 2.0 / 3.0; + bool center = (f.x >= T && f.x < T2 && f.y >= T && f.y < T2); + bool hit = false; + if (rt == 1u) { + hit = (f.x < T) || (f.x >= T2) || center; + } else if (rt == 2u) { + hit = (f.y < T) || (f.y >= T2) || center; + } else if (rt == 3u) { + hit = (f.y < T) || (f.x < T) || center; + } else if (rt == 4u) { + hit = (f.y < T) || (f.x >= T2) || center; + } else if (rt == 5u) { + hit = (f.y >= T2) || (f.x < T) || center; + } else if (rt == 6u) { + hit = (f.y >= T2) || (f.x >= T2) || center; + } + return hit ? 1.0 : 0.0; + } else { + // Simplified mode: fill entire tile (tiles are small at this zoom) + return 1.0; + } +} + +void main() { + ivec2 tc = ivec2(floor(vWorldPos)); + + if (tc.x < 0 || tc.y < 0 || tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) + discard; + + uint railType = texelFetch(uRailroadTex, tc, 0).r; + uint ghostRailType = texelFetch(uGhostRailTex, tc, 0).r; + vec2 f = fract(vWorldPos); + + // Compute coverage for real and ghost rails + float realCov = railCoverage(railType, f); + // Ghost only renders where there is no real rail (values 1-6 = ghost path) + // Value 7 = highlight marker (existing rail turns green) + float ghostCov = (ghostRailType >= 1u && ghostRailType <= 6u && railType == 0u) + ? railCoverage(ghostRailType, f) + : 0.0; + bool highlighted = (ghostRailType == 7u && railType != 0u); + + bool hitRail = (realCov * uRailAlpha > 0.001); + bool hitGhost = (ghostCov * uRailAlpha > 0.001); + + // --- Bridge: check 3x3 neighborhood for water+rail tiles --- + bool hitBridge = false; + ivec2 fp = ivec2(floor(vWorldPos * 2.0)); // fragment pos in game's 2x-pixel grid + + for (int dy = -1; dy <= 1 && !hitBridge; dy++) { + for (int dx = -1; dx <= 1 && !hitBridge; dx++) { + ivec2 ntc = tc + ivec2(dx, dy); + if (ntc.x < 0 || ntc.y < 0 || ntc.x >= int(uMapSize.x) || ntc.y >= int(uMapSize.y)) + continue; + uint nRail = texelFetch(uRailroadTex, ntc, 0).r; + if (nRail == 0u) continue; + uint nTerr = texelFetch(uTerrainTex, ntc, 0).r; + if ((nTerr & 0x80u) != 0u) continue; // land tile, no bridge + ivec2 lp = fp - ntc * 2; + if (isBridgePixel(nRail, lp)) hitBridge = true; + } + } + + if (!hitBridge && !hitRail && !hitGhost) discard; + + // --- Color output --- + vec3 bridgeColor = vec3(0.773, 0.271, 0.282); + + if (hitRail) { + float railAlpha = uRailAlpha * realCov; + uint tileRaw = texelFetch(uTileTex, tc, 0).r; + uint owner = tileRaw & uint(OWNER_MASK); + vec3 railColor = owner != 0u + ? texture(uPalette, vec2((float(owner) + 0.5) / float(PALETTE_SIZE), 0.75)).rgb + : vec3(0.75); + // Overlapping railroad highlight — green tint + if (highlighted) railColor = vec3(0.2, 0.85, 0.3); + if (hitBridge) { + fragColor = vec4(mix(bridgeColor, railColor, railAlpha), 1.0); + } else { + fragColor = vec4(railColor, railAlpha); + } + } else if (hitGhost) { + float ghostAlpha = uRailAlpha * ghostCov * 0.5; + vec3 ghostColor = uGhostOwnerID > 0.0 + ? texture(uPalette, vec2((uGhostOwnerID + 0.5) / float(PALETTE_SIZE), 0.75)).rgb + : vec3(0.75); + fragColor = vec4(ghostColor, ghostAlpha); + } else { + fragColor = vec4(bridgeColor, 1.0); + } +} diff --git a/src/client/render/gl/shaders/range-circle/range-circle.frag.glsl b/src/client/render/gl/shaders/range-circle/range-circle.frag.glsl new file mode 100644 index 0000000000..62744ab377 --- /dev/null +++ b/src/client/render/gl/shaders/range-circle/range-circle.frag.glsl @@ -0,0 +1,27 @@ +#version 300 es +precision highp float; + +in vec2 vLocal; // [-1, +1] + +uniform float uRadius; + +out vec4 fragColor; + +void main() { + float dist = length(vLocal) * (uRadius + 1.0); // world-space distance from center + float edge = uRadius; + + // Smooth fill: inside the circle at 20% white + float fill = 1.0 - smoothstep(edge - 0.5, edge + 0.5, dist); + + // Stroke: 1-tile-wide ring at the edge + float strokeInner = edge - 1.0; + float strokeOuter = edge; + float stroke = smoothstep(strokeInner - 0.5, strokeInner + 0.5, dist) + * (1.0 - smoothstep(strokeOuter - 0.5, strokeOuter + 0.5, dist)); + + float alpha = fill * 0.2 + stroke * 0.5; + if (alpha < 0.001) discard; + + fragColor = vec4(1.0, 1.0, 1.0, alpha); +} diff --git a/src/client/render/gl/shaders/range-circle/range-circle.vert.glsl b/src/client/render/gl/shaders/range-circle/range-circle.vert.glsl new file mode 100644 index 0000000000..407dbf57b5 --- /dev/null +++ b/src/client/render/gl/shaders/range-circle/range-circle.vert.glsl @@ -0,0 +1,24 @@ +#version 300 es +precision highp float; + +// Unit quad [0,1] +layout(location = 0) in vec2 aPos; + +uniform mat3 uCamera; +uniform vec2 uCenter; // world-space circle center (tile coords) +uniform float uRadius; // world-space radius in tiles + +out vec2 vLocal; // [-1, +1] local coords within the quad + +void main() { + // Map [0,1] → [-1,+1] + vLocal = aPos * 2.0 - 1.0; + + // Expand quad to cover circle bbox in world space + // Add 1-tile padding for the stroke + float r = uRadius + 1.0; + vec2 worldPos = uCenter + 0.5 + vLocal * r; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/sam-radius/sam-radius.frag.glsl b/src/client/render/gl/shaders/sam-radius/sam-radius.frag.glsl new file mode 100644 index 0000000000..a3c08ee5ae --- /dev/null +++ b/src/client/render/gl/shaders/sam-radius/sam-radius.frag.glsl @@ -0,0 +1,74 @@ +#version 300 es +precision highp float; + +in vec2 vLocal; +flat in float vRadius; +flat in vec3 vColor; +flat in vec2 vArcBounds; + +uniform float uTime; +uniform float uOutline; // 1.0 = owner mode (outline edges), 0.0 = perspective +uniform float uStrokeWidth; // ring half-width (world units) +uniform float uDashLen; // dash length (world units) +uniform float uGapLen; // gap length (world units) +uniform float uRotationSpeed; // rotation (world units/sec) +uniform float uAlpha; // base opacity +uniform float uOutlineWidth; // outline border width (world units) +uniform float uOutlineSoftness; // smoothstep range (0 = hard edge) + +out vec4 fragColor; + +const float TWO_PI = 6.2831853; + +void main() { + float paddedR = vRadius + 2.0; + float dist = length(vLocal) * paddedR; + + // Ring mask: stroke centered on the circle edge + float ringAlpha = smoothstep(vRadius - uStrokeWidth - 0.5, vRadius - uStrokeWidth, dist) + * (1.0 - smoothstep(vRadius + uStrokeWidth, vRadius + uStrokeWidth + 0.5, dist)); + + if (ringAlpha < 0.01) discard; + + // Arc clipping + float angle = atan(vLocal.y, vLocal.x); + float normAngle = angle < 0.0 ? angle + TWO_PI : angle; + bool fullCircle = vArcBounds.y - vArcBounds.x >= TWO_PI - 0.001; + if (!fullCircle) { + if (normAngle < vArcBounds.x || normAngle > vArcBounds.y) discard; + } + + // Dash pattern along circumference + float arcPos = angle * vRadius; + float period = uDashLen + uGapLen; + float dashPhase = mod(arcPos + uTime * uRotationSpeed, period); + float dashAlpha = 1.0 - smoothstep(uDashLen - 0.5, uDashLen + 0.5, dashPhase); + + float alpha = ringAlpha * dashAlpha * uAlpha; + if (alpha < 0.01) discard; + + // Outline: darken fragments near any edge of each dash segment + float edgeFade = 1.0; + if (uOutline > 0.5) { + float ow = uOutlineWidth; + float soft = uOutlineSoftness; + // Radial edges (inner/outer ring boundary) + float fromInner = dist - (vRadius - uStrokeWidth); + float fromOuter = (vRadius + uStrokeWidth) - dist; + edgeFade = min(smoothstep(ow - soft, ow + soft, fromInner), + smoothstep(ow - soft, ow + soft, fromOuter)); + // Dash start/end edges (circumferential) + edgeFade = min(edgeFade, smoothstep(ow - soft, ow + soft, dashPhase)); + edgeFade = min(edgeFade, smoothstep(ow - soft, ow + soft, uDashLen - dashPhase)); + // Arc endpoint edges (where circle union clips the arc) + if (!fullCircle) { + float arcDistStart = (normAngle - vArcBounds.x) * vRadius; + float arcDistEnd = (vArcBounds.y - normAngle) * vRadius; + edgeFade = min(edgeFade, smoothstep(ow - soft, ow + soft, arcDistStart)); + edgeFade = min(edgeFade, smoothstep(ow - soft, ow + soft, arcDistEnd)); + } + } + + vec3 finalColor = vColor * edgeFade; + fragColor = vec4(finalColor, alpha); +} diff --git a/src/client/render/gl/shaders/sam-radius/sam-radius.vert.glsl b/src/client/render/gl/shaders/sam-radius/sam-radius.vert.glsl new file mode 100644 index 0000000000..a0682028f0 --- /dev/null +++ b/src/client/render/gl/shaders/sam-radius/sam-radius.vert.glsl @@ -0,0 +1,33 @@ +#version 300 es +precision highp float; + +// Unit quad [0,1] +layout(location = 0) in vec2 aPos; +// Per-instance: x, y, radius +layout(location = 1) in vec3 aInstance; +// Per-instance: r, g, b +layout(location = 2) in vec3 aColor; +// Per-instance: arcStart, arcEnd +layout(location = 3) in vec2 aArcBounds; + +uniform mat3 uCamera; + +out vec2 vLocal; // [-1, +1] local coords +flat out float vRadius; // world-space radius for this instance +flat out vec3 vColor; // relationship color +flat out vec2 vArcBounds; // arc start/end in [0, 2PI) + +void main() { + vLocal = aPos * 2.0 - 1.0; + vRadius = aInstance.z; + vColor = aColor; + vArcBounds = aArcBounds; + + // Expand quad to cover circle bbox + padding for stroke + float r = aInstance.z + 2.0; + vec2 center = aInstance.xy + 0.5; + vec2 worldPos = center + vLocal * r; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/selection-box/selection-box.frag.glsl b/src/client/render/gl/shaders/selection-box/selection-box.frag.glsl new file mode 100644 index 0000000000..3d17fe2680 --- /dev/null +++ b/src/client/render/gl/shaders/selection-box/selection-box.frag.glsl @@ -0,0 +1,38 @@ +#version 300 es +precision highp float; + +in vec2 vWorld; + +uniform vec2 uCenter; +uniform float uHalfSize; +uniform float uTime; // frame tick counter (increments each frame) +uniform vec3 uColor; // RGB [0-1], already lightened + +out vec4 fragColor; + +void main() { + // Tile-space relative to center + vec2 tile = floor(vWorld); + vec2 rel = tile - floor(uCenter); + float hs = uHalfSize; + + // Is this tile on the border of the selection box? + bool inXRange = rel.x >= -hs && rel.x <= hs; + bool inYRange = rel.y >= -hs && rel.y <= hs; + bool isXEdge = abs(rel.x - hs) < 0.5 || abs(rel.x + hs) < 0.5; + bool isYEdge = abs(rel.y - hs) < 0.5 || abs(rel.y + hs) < 0.5; + + bool onBorder = (isXEdge && inYRange) || (isYEdge && inXRange); + if (!onBorder) discard; + + // Stipple: checkerboard pattern (every other tile) + float stipple = mod(tile.x + tile.y, 2.0); + if (stipple > 0.5) discard; + + // Pulsating alpha (matches game: base 200/255 ± 55/255) + float baseAlpha = 0.784; // 200/255 + float pulseAmp = 0.216; // 55/255 + float alpha = baseAlpha + sin(uTime * 0.1) * pulseAmp; + + fragColor = vec4(uColor, alpha); +} diff --git a/src/client/render/gl/shaders/selection-box/selection-box.vert.glsl b/src/client/render/gl/shaders/selection-box/selection-box.vert.glsl new file mode 100644 index 0000000000..03f2a86e46 --- /dev/null +++ b/src/client/render/gl/shaders/selection-box/selection-box.vert.glsl @@ -0,0 +1,22 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +uniform mat3 uCamera; +uniform vec2 uCenter; // world-space tile center (integer) +uniform float uHalfSize; // box half-size in tiles + +out vec2 vWorld; // world-space position + +void main() { + // Map [0,1] → [-1,+1] + vec2 local = aPos * 2.0 - 1.0; + + // Expand quad to cover box + 1-tile padding for AA + float r = uHalfSize + 1.0; + vWorld = uCenter + 0.5 + local * r; + + vec3 clip = uCamera * vec3(vWorld, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/shared/blur.frag.glsl b/src/client/render/gl/shaders/shared/blur.frag.glsl new file mode 100644 index 0000000000..dc445f9ee4 --- /dev/null +++ b/src/client/render/gl/shaders/shared/blur.frag.glsl @@ -0,0 +1,16 @@ +#version 300 es +precision highp float; +uniform sampler2D uTex; +uniform vec2 uDir; +in vec2 vUV; +out vec4 fragColor; +const float w[5] = float[5](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); +void main() { + vec4 result = texture(uTex, vUV) * w[0]; + for (int i = 1; i < 5; i++) { + vec2 off = uDir * float(i); + result += texture(uTex, vUV + off) * w[i]; + result += texture(uTex, vUV - off) * w[i]; + } + fragColor = result; +} diff --git a/src/client/render/gl/shaders/shared/fullscreen-no-uv.vert.glsl b/src/client/render/gl/shaders/shared/fullscreen-no-uv.vert.glsl new file mode 100644 index 0000000000..95a8b6944a --- /dev/null +++ b/src/client/render/gl/shaders/shared/fullscreen-no-uv.vert.glsl @@ -0,0 +1,5 @@ +#version 300 es +layout(location = 0) in vec2 aPos; +void main() { + gl_Position = vec4(aPos * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/src/client/render/gl/shaders/shared/fullscreen.vert.glsl b/src/client/render/gl/shaders/shared/fullscreen.vert.glsl new file mode 100644 index 0000000000..9a1858d753 --- /dev/null +++ b/src/client/render/gl/shaders/shared/fullscreen.vert.glsl @@ -0,0 +1,8 @@ +#version 300 es +precision highp float; +layout(location = 0) in vec2 aPos; +out vec2 vUV; +void main() { + gl_Position = vec4(aPos * 2.0 - 1.0, 0.0, 1.0); + vUV = aPos; +} diff --git a/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl b/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl new file mode 100644 index 0000000000..fd0578d822 --- /dev/null +++ b/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl @@ -0,0 +1,103 @@ +#version 300 es +precision highp float; +precision highp usampler2D; + +uniform usampler2D uTileTex; +uniform vec2 uMapSize; + +// Spawn center data packed as vec4 pairs: +// A[i] = (x, y, r, g) +// B[i] = (b, isSelf, isTeammate, _) +uniform vec4 uSpawnA[MAX_SPAWNS]; +uniform vec4 uSpawnB[MAX_SPAWNS]; +uniform int uSpawnCount; + +uniform float uBreathRadius; // normalized [0..1], animated via sin + +// Configurable parameters (from render settings) +uniform float uHighlightRadiusSq; // tile highlight radius squared +uniform float uHighlightAlpha; // tile highlight opacity +uniform vec4 uSelfRadii; // (minR, maxR, _, _) +uniform vec4 uMateRadii; // (minR, maxR, _, _) +uniform vec2 uGradientStops; // (innerEdge, solidEnd) + +in vec2 vWorldPos; +out vec4 fragColor; + +void main() { + ivec2 tc = ivec2(floor(vWorldPos)); + if (tc.x < 0 || tc.y < 0 || tc.x >= int(uMapSize.x) || tc.y >= int(uMapSize.y)) + discard; + + uint raw = texelFetch(uTileTex, tc, 0).r; + uint owner = raw & uint(OWNER_MASK); + bool unowned = (owner == 0u); + + vec4 result = vec4(0.0); + + for (int i = 0; i < MAX_SPAWNS; i++) { + if (i >= uSpawnCount) break; + + vec2 center = uSpawnA[i].xy; + vec3 color = vec3(uSpawnA[i].zw, uSpawnB[i].x); + float isSelf = uSpawnB[i].y; + float isTeammate = uSpawnB[i].z; + + float dx = vWorldPos.x - center.x; + float dy = vWorldPos.y - center.y; + float distSq = dx * dx + dy * dy; + float dist = sqrt(distSq); + + // --- Tile highlights (not for self or teammates) --- + if (isSelf < 0.5 && isTeammate < 0.5 && unowned && distSq <= uHighlightRadiusSq) { + float a = uHighlightAlpha; + result.rgb = mix(result.rgb, color, a * (1.0 - result.a)); + result.a = result.a + a * (1.0 - result.a); + } + + // --- Breathing rings (self or teammate only) --- + float minR, maxR; + if (isSelf > 0.5) { + minR = uSelfRadii.x; + maxR = uSelfRadii.y; + } else if (isTeammate > 0.5) { + minR = uMateRadii.x; + maxR = uMateRadii.y; + } else { + continue; + } + + // Static outer ring: radial gradient from minR to maxR + float range = maxR - minR; + float t = (dist - minR) / range; + if (t > 0.0 && t <= 1.0) { + float innerEdge = uGradientStops.x; + float solidEnd = uGradientStops.y; + float alpha; + if (t < innerEdge) { + alpha = t / innerEdge; + } else if (t < solidEnd) { + alpha = 1.0; + } else { + alpha = 1.0 - (t - solidEnd) / (1.0 - solidEnd); + } + + result.rgb = mix(result.rgb, color, alpha * (1.0 - result.a)); + result.a = result.a + alpha * (1.0 - result.a); + } + + // Breathing ring: solid colored disc from minR to breathR + float breathR = minR + range * uBreathRadius; + if (breathR > minR + 0.01) { + if (dist >= minR && dist <= breathR) { + float edge = smoothstep(minR, minR + 0.1, dist); + result.rgb = color; + result.a = max(result.a, edge); + } + } + } + + if (result.a < 0.001) discard; + // result is premultiplied; convert to straight for SRC_ALPHA blending + fragColor = vec4(result.rgb / result.a, result.a); +} diff --git a/src/client/render/gl/shaders/structure-level/structure-level.frag.glsl b/src/client/render/gl/shaders/structure-level/structure-level.frag.glsl new file mode 100644 index 0000000000..c4127fbdb2 --- /dev/null +++ b/src/client/render/gl/shaders/structure-level/structure-level.frag.glsl @@ -0,0 +1,50 @@ +#version 300 es +precision highp float; + +uniform sampler2D uAtlas; +uniform float uDistRange; +uniform float uOutlineWidth; +uniform int uHighlightMask; +uniform float uHighlightDimAlpha; + +in vec2 vUV; +flat in float vAlive; +flat in float vAtlasIdx; +out vec4 fragColor; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + if (vAlive <= 0.0) discard; + + vec3 msd = texture(uAtlas, vUV).rgb; + float sd = median(msd.r, msd.g, msd.b); + + vec2 unitRange = uDistRange / vec2(textureSize(uAtlas, 0)); + vec2 screenTexSize = 1.0 / fwidth(vUV); + float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); + + float screenPxDist = screenPxRange * (sd - 0.5); + float fillAlpha = clamp(screenPxDist + 0.5, 0.0, 1.0); + + // White text with dark outline + float maxOutline = max(screenPxRange * 0.5 - 1.0, 0.0); + float effectiveOutline = min(uOutlineWidth, maxOutline); + float outlineDist = screenPxDist + effectiveOutline; + float outlineAlpha = clamp(outlineDist + 0.5, 0.0, 1.0); + + vec3 color = mix(vec3(0.0), vec3(1.0), fillAlpha); + float finalAlpha = outlineAlpha; + + // Dim level text for non-highlighted structure types + if (uHighlightMask != 0) { + int bit = 1 << int(vAtlasIdx + 0.5); + if ((uHighlightMask & bit) == 0) { + finalAlpha *= uHighlightDimAlpha; + } + } + + fragColor = vec4(color, finalAlpha); +} diff --git a/src/client/render/gl/shaders/structure-level/structure-level.vert.glsl b/src/client/render/gl/shaders/structure-level/structure-level.vert.glsl new file mode 100644 index 0000000000..27ef723cac --- /dev/null +++ b/src/client/render/gl/shaders/structure-level/structure-level.vert.glsl @@ -0,0 +1,96 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +// Per-instance: worldX, worldY, cursorX, charCode +layout(location = 1) in vec4 aInst; +layout(location = 2) in float aAtlasIdx; + +uniform sampler2D uGlyphMetrics; // CHAR_RANGE x 2, RGBA32F + +uniform mat3 uCamera; +uniform float uZoom; + +// Structure icon sizing (mirrors structure.vert.glsl) +uniform float uIconSize; +uniform float uDotsThreshold; +uniform float uScaleFactor; + +// Text sizing +uniform float uFontSize; +uniform float uAtlasScaleH; +uniform float uBase; +uniform float uLevelScale; + +out vec2 vUV; +flat out float vAlive; +flat out float vAtlasIdx; + +void main() { + float worldX = aInst.x; + float worldY = aInst.y; + float cursorX = aInst.z; + int charCode = int(aInst.w); + + // Same icon scale logic as structure.vert.glsl + float iconScale; + if (uZoom <= uDotsThreshold) { + iconScale = 0.0; // hidden in dots mode + } else { + iconScale = min(1.0, uZoom / uScaleFactor); + } + + // Cull when invisible + if (iconScale <= 0.0 || charCode == 0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vAlive = 0.0; + return; + } + vAlive = 1.0; + vAtlasIdx = aAtlasIdx; + + float halfIconSize = uIconSize * iconScale * 0.5 / uZoom; + + // Level text scale: proportional to icon size + float levelScale = halfIconSize * uLevelScale / uFontSize; + + // Glyph metrics from data texture + vec4 m0 = texelFetch(uGlyphMetrics, ivec2(charCode, 0), 0); // xadvance, xoffset, yoffset, width + vec4 m1 = texelFetch(uGlyphMetrics, ivec2(charCode, 1), 0); // height, u0, v0, u1 + + float glyphW = m0.w; + float glyphH = m1.x; + float u0 = m1.y; + float v0 = m1.z; + float u1 = m1.w; + float v1 = v0 + glyphH / uAtlasScaleH; + + // Skip degenerate glyphs + if (glyphW <= 0.0 || glyphH <= 0.0) { + gl_Position = vec4(0.0); + vUV = vec2(0.0); + vAlive = 0.0; + return; + } + + // Position above icon center + vec2 center = vec2(worldX + 0.5, worldY + 0.5); + float baselineY = -uBase * 0.5; + float yOff = -halfIconSize - levelScale * uBase * 0.6; // above icon top edge + + vec2 glyphOrigin = vec2( + cursorX + m0.y, // + xoffset + baselineY + m0.z // + yoffset + ) * levelScale; + + vec2 glyphSize = vec2(glyphW, glyphH) * levelScale; + + vec2 worldPos = center + vec2(0.0, yOff) + glyphOrigin + aPos * glyphSize; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vUV = vec2(mix(u0, u1, aPos.x), mix(v0, v1, aPos.y)); +} diff --git a/src/client/render/gl/shaders/structure/structure.frag.glsl b/src/client/render/gl/shaders/structure/structure.frag.glsl new file mode 100644 index 0000000000..677bb3b848 --- /dev/null +++ b/src/client/render/gl/shaders/structure/structure.frag.glsl @@ -0,0 +1,168 @@ +#version 300 es +precision highp float; + +uniform sampler2D uPalette; +uniform sampler2D uAtlas; +uniform sampler2D uAffiliation; // 256×2 RGBA8 — row 1 = unit affiliation +uniform float uDotsThreshold; +uniform float uGhostAlpha; // 1.0 = normal, <1.0 = ghost transparency +uniform vec3 uOutlineColor; // ghost outline color (vec3(0) = no outline) +uniform int uAltView; +uniform int uHighlightMask; // bitmask of atlas columns to highlight (0 = off) +uniform float uHighlightOutlineW; // outline width for highlighted structures +uniform float uHighlightDimAlpha; // alpha multiplier for non-highlighted structures + +in vec2 vLocalPos; +in vec2 vAtlasUV; +flat in float vOwnerID; +flat in float vUnderConstruction; +flat in float vMarkedForDeletion; +flat in float vZoom; +flat in float vAtlasIdx; +flat in float vShapeScale; + +out vec4 fragColor; + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec3 p = abs(fract(c.xxx + vec3(1.0, 2.0/3.0, 1.0/3.0)) * 6.0 - 3.0); + return c.z * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y); +} + +vec3 darken(vec3 rgb, float vScale) { + vec3 hsv = rgb2hsv(rgb); + hsv.z *= vScale; + return hsv2rgb(hsv); +} + +#define PI 3.14159265 + +// Signed distance to regular polygon edge. +// R = circumradius (center-to-vertex), n = sides, rot = rotation in radians. +// Returns negative inside, positive outside. +float sdPolygon(vec2 p, float R, float n, float rot) { + float an = PI / n; + float a = atan(p.y, p.x) - rot; + a = mod(a + an, 2.0 * an) - an; + return length(p) * cos(a) - R * cos(an); +} + +// Per-structure-type shape SDF. +// Atlas indices: 0=City, 1=Port, 2=Factory, 3=DefensePost, 4=SAM, 5=Silo +float shapeSDF(vec2 p, float R) { + if (vAtlasIdx < 0.5 || (vAtlasIdx > 1.5 && vAtlasIdx < 2.5)) + return length(p) - R; // City / Factory → circle + if (vAtlasIdx < 1.5) + return sdPolygon(p, R, 5.0, PI * 0.5); // Port → pentagon (vertex up) + if (vAtlasIdx < 3.5) + return sdPolygon(p, R, 8.0, 0.0); // Defense Post → octagon (flat top) + if (vAtlasIdx < 4.5) + return sdPolygon(p, R, 4.0, 0.0); // SAM Launcher → square (flat sides) + return sdPolygon(p, R, 3.0, PI * 0.5); // Missile Silo → triangle (vertex up) +} + +void main() { + float dist = length(vLocalPos); + float radius = 0.45; + float borderWidth = 0.06 / vShapeScale; + + float sdf = shapeSDF(vLocalPos, radius); + float fw = fwidth(dist); + + // When highlight is active, expand the region to include the outer outline band. + float highlightOutlineW = uHighlightMask != 0 ? uHighlightOutlineW / vShapeScale : 0.0; + float outerAlpha = 1.0 - smoothstep(-fw, fw, sdf - highlightOutlineW); + + if (outerAlpha <= 0.0) discard; + + float borderMask = 1.0 - smoothstep(-fw, fw, sdf + borderWidth); + + // Player color + vec4 fillColor; + vec4 borderColor; + + if (uAltView != 0 && vUnderConstruction < 0.5) { + vec3 ac = texelFetch(uAffiliation, ivec2(int(vOwnerID), 1), 0).rgb; + fillColor = vec4(darken(ac, 0.65), 1.0); + borderColor = vec4(darken(ac, 0.35), 1.0); + } else if (vUnderConstruction > 0.5) { + fillColor = vec4(198.0/255.0, 198.0/255.0, 198.0/255.0, 1.0); + borderColor = vec4(127.0/255.0, 127.0/255.0, 127.0/255.0, 1.0); + } else { + float u = (vOwnerID + 0.5) / float(PALETTE_SIZE); + fillColor = texture(uPalette, vec2(u, 0.25)); + borderColor = texture(uPalette, vec2(u, 0.75)); + // Darken via HSV value so hue/saturation stay intact + // vScale < 1.0 = darker, > 1.0 = brighter + fillColor.rgb = darken(fillColor.rgb, 0.65); + borderColor.rgb = darken(borderColor.rgb, 0.35); + fillColor.a = 1.0; + borderColor.a = 1.0; + } + + vec4 bgColor = mix(borderColor, fillColor, borderMask); + + // Sample icon from atlas (white on transparent) + // Only show icon detail when zoomed in enough + float iconAlpha = 0.0; + if (vZoom > uDotsThreshold) { + // Clamp UV to this atlas column to prevent bleeding into neighbours + // when uIconFill shrinks the icon (expanding UV range beyond column). + float colStart = vAtlasIdx / float(ATLAS_COLS); + float colEnd = (vAtlasIdx + 1.0) / float(ATLAS_COLS); + vec2 safeUV = vec2(clamp(vAtlasUV.x, colStart, colEnd), clamp(vAtlasUV.y, 0.0, 1.0)); + vec4 iconSample = texture(uAtlas, safeUV); + // Zero out icon outside the valid UV region (clamped pixels would repeat the edge) + float inBounds = step(colStart, vAtlasUV.x) * step(vAtlasUV.x, colEnd) + * step(0.0, vAtlasUV.y) * step(vAtlasUV.y, 1.0); + // Clip to fill area so icon doesn't bleed into the border ring. + iconAlpha = iconSample.a * borderMask * inBounds; + } + + // Composite: white icon over player-colored shape + vec3 finalRGB = mix(bgColor.rgb, vec3(1.0), iconAlpha); + + // Red X overlay for units marked for deletion + if (vMarkedForDeletion > 0.5) { + float lineW = max(0.025, fw * 1.5); + float d1 = abs(vLocalPos.x - vLocalPos.y) * 0.7071; // dist to y=x diagonal + float d2 = abs(vLocalPos.x + vLocalPos.y) * 0.7071; // dist to y=-x diagonal + float dMin = min(d1, d2); + // Extend arms close to the circle edge + float maskR = max(radius * 1.55, fw * 6.0); + float mask = 1.0 - smoothstep(maskR - fw, maskR, dist); + float xLine = (1.0 - smoothstep(lineW - fw, lineW + fw, dMin)) * mask; + finalRGB = mix(finalRGB, vec3(1.0, 0.25, 0.25), xLine * 0.95); + } + + // Ghost tint — blend entire surface toward uOutlineColor when non-zero + float tintActive = step(0.01, dot(uOutlineColor, uOutlineColor)); + finalRGB = mix(finalRGB, uOutlineColor, tintActive * 0.5); + + float finalAlpha = bgColor.a * outerAlpha * uGhostAlpha; + + // Build-button hover highlight: white outline on matching types, dim the rest + if (uHighlightMask != 0) { + int bit = 1 << int(vAtlasIdx + 0.5); + if ((uHighlightMask & bit) != 0) { + // White outline band outside the shape edge (matches game's OutlineFilter) + float shapeEdge = 1.0 - smoothstep(-fw, fw, sdf); // 1 inside shape, 0 outside + float expandedEdge = 1.0 - smoothstep(-fw, fw, sdf - highlightOutlineW); // includes outline band + float outlineBand = expandedEdge - shapeEdge; // 1 in outline region only + finalRGB = mix(finalRGB, vec3(1.0), outlineBand); + finalAlpha = max(finalAlpha, outlineBand); + } else { + finalAlpha *= uHighlightDimAlpha; + } + } + + fragColor = vec4(finalRGB, finalAlpha); +} diff --git a/src/client/render/gl/shaders/structure/structure.vert.glsl b/src/client/render/gl/shaders/structure/structure.vert.glsl new file mode 100644 index 0000000000..a0b1a6d756 --- /dev/null +++ b/src/client/render/gl/shaders/structure/structure.vert.glsl @@ -0,0 +1,65 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +// Per-instance: x, y, ownerID, underConstruction, atlasIdx, markedForDeletion +layout(location = 1) in vec4 aInst0; // x, y, ownerID, underConstruction +layout(location = 2) in vec2 aInst1; // atlasIdx, markedForDeletion + +uniform mat3 uCamera; +uniform float uZoom; + +uniform float uIconSize; +uniform float uDotsThreshold; +uniform float uScaleFactor; +uniform float uShapeScales[ATLAS_COLS]; +uniform float uIconFills[ATLAS_COLS]; + +out vec2 vLocalPos; +out vec2 vAtlasUV; +flat out float vOwnerID; +flat out float vUnderConstruction; +flat out float vMarkedForDeletion; +flat out float vZoom; +flat out float vAtlasIdx; +flat out float vShapeScale; + +void main() { + float worldX = aInst0.x; + float worldY = aInst0.y; + vOwnerID = aInst0.z; + vUnderConstruction = aInst0.w; + vMarkedForDeletion = aInst1.y; + vZoom = uZoom; + vAtlasIdx = aInst1.x; + + float iconScale; + if (uZoom <= uDotsThreshold) { + iconScale = 1.0 / 2.5; + } else { + iconScale = min(1.0, uZoom / uScaleFactor); + } + + int shapeIdx = int(aInst1.x); + float shapeScale = uShapeScales[shapeIdx]; + vShapeScale = shapeScale; + + float halfSize = uIconSize * iconScale * 0.5 / uZoom * shapeScale; + + vec2 center = vec2(worldX + 0.5, worldY + 0.5); + vec2 worldPos = center + (aPos - 0.5) * halfSize * 2.0; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vLocalPos = aPos - 0.5; + + // Atlas UV: icons stay the same world size regardless of shape scaling, + // and are further shrunk by per-shape iconFill (0-1) to add padding inside the frame. + float uvExpand = shapeScale / uIconFills[shapeIdx]; + float scaledX = 0.5 + (aPos.x - 0.5) * uvExpand; + float scaledY = 0.5 + (aPos.y - 0.5) * uvExpand; + float colU = (aInst1.x + scaledX) / float(ATLAS_COLS); + vAtlasUV = vec2(colU, scaledY); +} diff --git a/src/client/render/gl/shaders/terrain/terrain.frag.glsl b/src/client/render/gl/shaders/terrain/terrain.frag.glsl new file mode 100644 index 0000000000..869bfff503 --- /dev/null +++ b/src/client/render/gl/shaders/terrain/terrain.frag.glsl @@ -0,0 +1,11 @@ +#version 300 es +precision highp float; + +uniform sampler2D uTerrain; + +in vec2 vUV; +out vec4 fragColor; + +void main() { + fragColor = texture(uTerrain, vUV); +} diff --git a/src/client/render/gl/shaders/terrain/terrain.vert.glsl b/src/client/render/gl/shaders/terrain/terrain.vert.glsl new file mode 100644 index 0000000000..a712bada22 --- /dev/null +++ b/src/client/render/gl/shaders/terrain/terrain.vert.glsl @@ -0,0 +1,17 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +uniform mat3 uCamera; + +out vec2 vUV; + +void main() { + vec3 clip = uCamera * vec3(aPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + // aPos ranges [0, mapW] × [0, mapH] — normalize to [0,1] UV + // (mapSize is baked into the quad vertices, so we pass it as a uniform) + vUV = aPos / vec2(float(MAP_W), float(MAP_H)); +} diff --git a/src/client/render/gl/shaders/unit/unit.frag.glsl b/src/client/render/gl/shaders/unit/unit.frag.glsl new file mode 100644 index 0000000000..2667082339 --- /dev/null +++ b/src/client/render/gl/shaders/unit/unit.frag.glsl @@ -0,0 +1,93 @@ +#version 300 es +precision highp float; + +uniform sampler2D uPalette; +uniform sampler2D uAtlas; +uniform sampler2D uAffiliation; // 256×2 RGBA8 — row 1 = unit affiliation +uniform float uTick; +uniform float uFlickerSpeed; +uniform vec3 uAngryColor; +uniform int uAltView; + +in vec2 vLocalPos; +in vec2 vAtlasUV; +flat in float vOwnerID; +flat in float vFlags; +flat in float vHash; + +out vec4 fragColor; + +// Flag constants — must match CPU-side FLAG_* values +const float FLAG_FLICKER = 1.0; +const float FLAG_ANGRY = 2.0; +const float FLAG_TRADE_FRIENDLY = 3.0; + +// Ally color for trade-friendly override (yellow — matches affiliation.ts ALLY) +const vec3 ALLY_COLOR = vec3(1.0, 1.0, 0.0); + +// Flicker hot colors: red → orange → yellow → white +const vec3 FLICKER_COLORS[4] = vec3[4]( + vec3(1.0, 0.0, 0.0), // red + vec3(1.0, 0.5, 0.0), // orange + vec3(1.0, 1.0, 0.0), // yellow + vec3(1.0, 1.0, 1.0) // white +); + +void main() { + vec4 texel = texture(uAtlas, vAtlasUV); + + // Discard fully transparent pixels + if (texel.a < 0.01) discard; + + float gray = texel.r; + + // Alt-view: solid affiliation color, no gray-replacement bands + if (uAltView != 0) { + // Enemy trade ships heading to a self/allied port render as yellow (ally) + vec3 ac = vFlags > 2.5 + ? ALLY_COLOR + : texelFetch(uAffiliation, ivec2(int(vOwnerID), 1), 0).rgb; + fragColor = vec4(ac, texel.a); + return; + } + + // Player color lookup from palette + float u = (vOwnerID + 0.5) / float(PALETTE_SIZE); + vec3 territoryColor = texture(uPalette, vec2(u, 0.25)).rgb; + vec3 borderColor = texture(uPalette, vec2(u, 0.75)).rgb; + + // Flag states (uint8 passed as float via vertex attribute): + // 0 = normal + // 1 = flicker (nukes/warheads — cycling hot colors) + // 2 = angry (warships attacking — solid red territory band) + if (vFlags > 1.5) { + // Angry: solid red territory band + territoryColor = uAngryColor; + } else if (vFlags > 0.5) { + // Flicker: cycle through hot colors, offset by position hash + float phase = fract(uTick * uFlickerSpeed + vHash); + int idx = int(phase * 4.0) % 4; + territoryColor = FLICKER_COLORS[idx]; + borderColor = FLICKER_COLORS[(idx + 2) % 4]; + } + + // Three-band gray replacement: + // 180/255 ~ 0.706 -> territory color (light band) + // 130/255 ~ 0.510 -> spawn/mid color (interpolated) + // 70/255 ~ 0.275 -> border color (dark band) + vec3 spawnColor = mix(territoryColor, borderColor, 0.5); + + vec3 color; + if (gray > 0.6) { + // Light band (180) -> territory color + color = territoryColor; + } else if (gray > 0.4) { + // Mid band (130) -> spawn color + color = spawnColor; + } else { + // Dark band (70) -> border color + color = borderColor; + } + + fragColor = vec4(color, texel.a); +} diff --git a/src/client/render/gl/shaders/unit/unit.vert.glsl b/src/client/render/gl/shaders/unit/unit.vert.glsl new file mode 100644 index 0000000000..640734d984 --- /dev/null +++ b/src/client/render/gl/shaders/unit/unit.vert.glsl @@ -0,0 +1,46 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec2 aPos; + +// Per-instance attributes +layout(location = 1) in vec3 aInstPos; // x, y, ownerID +layout(location = 2) in vec2 aInstFlags; // atlasIdx (uint8→float), flags (uint8→float) + +uniform mat3 uCamera; + +uniform float uUnitSize; + +out vec2 vLocalPos; +out vec2 vAtlasUV; +flat out float vOwnerID; +flat out float vFlags; // 0.0 = normal, 1.0 = flicker, 2.0 = angry +flat out float vHash; // per-instance hash for flicker phase offset + +void main() { + float worldX = aInstPos.x; + float worldY = aInstPos.y; + vOwnerID = aInstPos.z; + + float atlasCol = aInstFlags.x; + vFlags = aInstFlags.y; + + // Position-based hash so each unit flickers independently + vHash = fract(worldX * 0.1731 + worldY * 0.3179); + + // UNIT_SIZE is in world-space tiles — no zoom division needed. + // Units scale with the map like territory tiles do. + float halfSize = uUnitSize * 0.5; + + vec2 center = vec2(worldX + 0.5, worldY + 0.5); + vec2 worldPos = center + (aPos - 0.5) * halfSize * 2.0; + + vec3 clip = uCamera * vec3(worldPos, 1.0); + gl_Position = vec4(clip.xy, 0.0, 1.0); + + vLocalPos = aPos; + + // Atlas UV: map quad [0,1] to the correct column + float colU = (atlasCol + aPos.x) / float(ATLAS_COLS); + vAtlasUV = vec2(colU, aPos.y); +} diff --git a/src/client/render/gl/utils/affiliation.ts b/src/client/render/gl/utils/affiliation.ts new file mode 100644 index 0000000000..1dbda6b054 --- /dev/null +++ b/src/client/render/gl/utils/affiliation.ts @@ -0,0 +1,171 @@ +/** + * Affiliation palette — maps ownerID → affiliation color for alt-view. + * + * TEX_W×2 RGBA8 texture (TEX_W = PALETTE_SIZE = 4096): + * Row 0: border colors (4-state: self/ally/neutral/embargo) + * Row 1: unit colors (3-state: self/ally/enemy) + * + * Rebuilt when localPlayerID or relationship data changes. + */ + +import { getPaletteSize } from "./color-utils"; +import { createTexture2D } from "./gl-utils"; + +// Relationship constants (must match adapter.ts) +const RELATION_NEUTRAL = 0; +const RELATION_FRIENDLY = 1; +const RELATION_EMBARGO = 2; + +// Affiliation RGB values (upstream PastelTheme) +const SELF_R = 0, + SELF_G = 255, + SELF_B = 0; +const ALLY_R = 255, + ALLY_G = 255, + ALLY_B = 0; +const NEUTRAL_R = 128, + NEUTRAL_G = 128, + NEUTRAL_B = 128; +const ENEMY_R = 255, + ENEMY_G = 0, + ENEMY_B = 0; + +const TEX_W = getPaletteSize(); // 4096 — covers full 12-bit smallID range +const TEX_H = 2; + +export class AffiliationPalette { + private gl: WebGL2RenderingContext; + private tex: WebGLTexture; + private cpuData = new Uint8Array(TEX_W * TEX_H * 4); + private dirty = false; + + // Cached inputs for rebuilding + private localPlayerID = 0; + private relationData: Uint8Array | null = null; + private relationSize = 0; + + constructor(gl: WebGL2RenderingContext) { + this.gl = gl; + this.rebuild(); // initialize to spectator-mode defaults (gray borders, red units) + this.tex = createTexture2D(gl, { + width: TEX_W, + height: TEX_H, + internalFormat: gl.RGBA8, + format: gl.RGBA, + type: gl.UNSIGNED_BYTE, + data: this.cpuData, + filter: gl.NEAREST, + }); + this.dirty = false; // already baked into initial upload + } + + getTexture(): WebGLTexture { + return this.tex; + } + + setLocalPlayer(id: number): void { + if (id === this.localPlayerID) return; + this.localPlayerID = id; + this.rebuild(); + } + + updateRelations(data: Uint8Array, size: number): void { + this.relationData = data; + this.relationSize = size; + this.rebuild(); + } + + /** Flush to GPU if dirty (call before drawing alt-view passes). */ + flush(): void { + if (!this.dirty) return; + const gl = this.gl; + gl.bindTexture(gl.TEXTURE_2D, this.tex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + TEX_W, + TEX_H, + gl.RGBA, + gl.UNSIGNED_BYTE, + this.cpuData, + ); + this.dirty = false; + } + + private rebuild(): void { + const d = this.cpuData; + const lp = this.localPlayerID; + const rel = this.relationData; + const rs = this.relationSize; + + for (let owner = 0; owner < TEX_W; owner++) { + // Determine relationship + let relation = RELATION_NEUTRAL; + if (rel && lp > 0 && owner > 0 && owner < rs && lp < rs) { + relation = rel[lp * rs + owner]; + } + const isSelf = owner > 0 && owner === lp; + + // Row 0: border colors (4-state) + const bOff = owner * 4; + if (owner === 0) { + d[bOff] = 0; + d[bOff + 1] = 0; + d[bOff + 2] = 0; + d[bOff + 3] = 0; + } else if (isSelf) { + d[bOff] = SELF_R; + d[bOff + 1] = SELF_G; + d[bOff + 2] = SELF_B; + d[bOff + 3] = 255; + } else if (relation === RELATION_FRIENDLY) { + d[bOff] = ALLY_R; + d[bOff + 1] = ALLY_G; + d[bOff + 2] = ALLY_B; + d[bOff + 3] = 255; + } else if (relation === RELATION_EMBARGO) { + d[bOff] = ENEMY_R; + d[bOff + 1] = ENEMY_G; + d[bOff + 2] = ENEMY_B; + d[bOff + 3] = 255; + } else { + d[bOff] = NEUTRAL_R; + d[bOff + 1] = NEUTRAL_G; + d[bOff + 2] = NEUTRAL_B; + d[bOff + 3] = 255; + } + + // Row 1: unit colors (3-state — no neutral, neutral→enemy) + const uOff = (TEX_W + owner) * 4; + if (owner === 0) { + d[uOff] = 0; + d[uOff + 1] = 0; + d[uOff + 2] = 0; + d[uOff + 3] = 0; + } else if (isSelf) { + d[uOff] = SELF_R; + d[uOff + 1] = SELF_G; + d[uOff + 2] = SELF_B; + d[uOff + 3] = 255; + } else if (relation === RELATION_FRIENDLY) { + d[uOff] = ALLY_R; + d[uOff + 1] = ALLY_G; + d[uOff + 2] = ALLY_B; + d[uOff + 3] = 255; + } else { + d[uOff] = ENEMY_R; + d[uOff + 1] = ENEMY_G; + d[uOff + 2] = ENEMY_B; + d[uOff + 3] = 255; + } + } + + this.dirty = true; + } + + dispose(): void { + this.gl.deleteTexture(this.tex); + } +} diff --git a/src/client/render/gl/utils/color-utils.ts b/src/client/render/gl/utils/color-utils.ts new file mode 100644 index 0000000000..68b008c451 --- /dev/null +++ b/src/client/render/gl/utils/color-utils.ts @@ -0,0 +1,90 @@ +/** + * GPU-ready color utilities. + * + * Terrain RGBA: Uint8Array(w × h × 4) — one RGBA pixel per tile, computed + * from PastelTheme rules applied to the raw terrain byte layout. + * + * Player palette is NOT built here — consumers provide a pre-built + * Float32Array(PALETTE_SIZE × 2 × 4) to the GPURenderer constructor. + */ + +/** Must cover 12-bit smallID range (0-4095). */ +const PALETTE_SIZE = 4096; + +export function getPaletteSize(): number { + return PALETTE_SIZE; +} + +// ---------- Terrain ---------- + +/** + * Compute a static RGBA8 texture from raw terrain bytes. + * Replicates PastelTheme.terrainColor() on the CPU. + * + * Terrain byte layout per tile: + * bit 7: isLand + * bit 6: isShoreline + * bit 5: isOcean (water only) + * bits 0-4: magnitude (0-31) + */ +export function buildTerrainRGBA( + terrainBytes: Uint8Array, + w: number, + h: number, +): Uint8Array { + const pixels = new Uint8Array(w * h * 4); + + for (let i = 0; i < w * h; i++) { + const tb = terrainBytes[i]; + const isLand = (tb & 0x80) !== 0; + const isShoreline = (tb & 0x40) !== 0; + const magnitude = tb & 0x1f; + + let r: number, g: number, b: number; + + if (isLand && isShoreline) { + // Shore (sand) + r = 204; + g = 203; + b = 158; + } else if (isLand) { + if (magnitude < 10) { + // Plains + r = 190; + g = 220 - 2 * magnitude; + b = 138; + } else if (magnitude < 20) { + // Highland + r = 200 + 2 * magnitude; + g = 183 + 2 * magnitude; + b = 138 + 2 * magnitude; + } else { + // Mountain + const v = Math.min(255, 230 + Math.floor(magnitude / 2)); + r = v; + g = v; + b = v; + } + } else if (isShoreline) { + // Shoreline water + r = 100; + g = 143; + b = 255; + } else { + // Deep water + const m = Math.min(magnitude, 10); + const offset = 11 - m; + r = Math.max(0, 70 - 10 + offset); + g = Math.max(0, 132 - 10 + offset); + b = Math.max(0, 180 - 10 + offset); + } + + const off = i * 4; + pixels[off] = r; + pixels[off + 1] = g; + pixels[off + 2] = b; + pixels[off + 3] = 255; + } + + return pixels; +} diff --git a/src/client/render/gl/utils/gl-utils.ts b/src/client/render/gl/utils/gl-utils.ts new file mode 100644 index 0000000000..53b59abc03 --- /dev/null +++ b/src/client/render/gl/utils/gl-utils.ts @@ -0,0 +1,209 @@ +/** + * WebGL2 utility functions: shader compilation, texture creation, VAO helpers. + */ + +export function compileShader( + gl: WebGL2RenderingContext, + type: number, + source: string, +): WebGLShader { + const shader = gl.createShader(type)!; + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + const log = gl.getShaderInfoLog(shader) ?? ""; + gl.deleteShader(shader); + throw new Error(`Shader compile error:\n${log}`); + } + return shader; +} + +export function createProgram( + gl: WebGL2RenderingContext, + vertSrc: string, + fragSrc: string, +): WebGLProgram { + const vs = compileShader(gl, gl.VERTEX_SHADER, vertSrc); + const fs = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc); + const program = gl.createProgram()!; + gl.attachShader(program, vs); + gl.attachShader(program, fs); + gl.linkProgram(program); + gl.deleteShader(vs); + gl.deleteShader(fs); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + const log = gl.getProgramInfoLog(program) ?? ""; + gl.deleteProgram(program); + throw new Error(`Program link error:\n${log}`); + } + return program; +} + +export interface TextureOpts { + width: number; + height: number; + internalFormat: number; + format: number; + type: number; + data: ArrayBufferView | null; + filter?: number; + wrap?: number; +} + +export function createTexture2D( + gl: WebGL2RenderingContext, + opts: TextureOpts, +): WebGLTexture { + const tex = gl.createTexture()!; + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MIN_FILTER, + opts.filter ?? gl.NEAREST, + ); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_MAG_FILTER, + opts.filter ?? gl.NEAREST, + ); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_WRAP_S, + opts.wrap ?? gl.CLAMP_TO_EDGE, + ); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_WRAP_T, + opts.wrap ?? gl.CLAMP_TO_EDGE, + ); + gl.texImage2D( + gl.TEXTURE_2D, + 0, + opts.internalFormat, + opts.width, + opts.height, + 0, + opts.format, + opts.type, + opts.data, + ); + return tex; +} + +/** + * Create a VAO with a quad covering [0,0]→[mapWidth, mapHeight] in world coords. + * Two triangles, positions only. Attribute location 0. + */ +/** + * Create a VAO with a [0,1]² fullscreen quad. Two triangles, positions only. + * Attribute location 0. Used for post-process passes (blur, composite, etc.). + */ +export function createFullscreenQuad( + gl: WebGL2RenderingContext, +): WebGLVertexArrayObject { + const vao = gl.createVertexArray()!; + gl.bindVertexArray(vao); + + const buf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1]), + gl.STATIC_DRAW, + ); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + gl.bindVertexArray(null); + return vao; +} + +/** + * Inject `#define` constants into a GLSL shader source string. + * Inserts definitions immediately after the `#version` line. + * + * Usage: + * shaderSrc(blurFrag, { PALETTE_SIZE: 4096 }) + * // → "#version 300 es\n#define PALETTE_SIZE 4096\n..." + */ +export function shaderSrc( + source: string, + defines: Record, +): string { + const defs = Object.entries(defines) + .map(([k, v]) => `#define ${k} ${v}`) + .join("\n"); + return source.replace("#version 300 es", `#version 300 es\n${defs}`); +} + +export interface RenderTarget { + fbo: WebGLFramebuffer; + tex: WebGLTexture; + w: number; + h: number; +} + +/** + * Bind a render target FBO, set viewport, clear, run draw callback, then + * restore the default framebuffer. Returns the target texture for chaining. + */ +export function toTarget( + gl: WebGL2RenderingContext, + target: RenderTarget, + draw: () => void, +): WebGLTexture { + gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo); + gl.viewport(0, 0, target.w, target.h); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + draw(); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return target.tex; +} + +/** + * Bind the screen (default framebuffer), set viewport, run draw callback. + */ +export function toScreen( + gl: WebGL2RenderingContext, + w: number, + h: number, + draw: () => void, +): void { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.viewport(0, 0, w, h); + draw(); +} + +export function createMapQuad( + gl: WebGL2RenderingContext, + mapWidth: number, + mapHeight: number, +): WebGLVertexArrayObject { + const vao = gl.createVertexArray()!; + gl.bindVertexArray(vao); + + const positions = new Float32Array([ + 0, + 0, + mapWidth, + 0, + 0, + mapHeight, + 0, + mapHeight, + mapWidth, + 0, + mapWidth, + mapHeight, + ]); + + const buf = gl.createBuffer()!; + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + gl.bindVertexArray(null); + return vao; +} diff --git a/src/client/render/gl/utils/gpu-resources.ts b/src/client/render/gl/utils/gpu-resources.ts new file mode 100644 index 0000000000..7f86060ba1 --- /dev/null +++ b/src/client/render/gl/utils/gpu-resources.ts @@ -0,0 +1,78 @@ +/** + * GPUResources — shared GPU textures created once, passed to all passes. + * + * Eliminates getter chains, setBorderTex/setHeatTex late-wiring, and + * construction-order dependencies between passes. + */ + +import { createTexture2D } from "./gl-utils"; + +export interface GPUResources { + tileTex: WebGLTexture; // R16UI — tile ownership + flags + trailTex: WebGLTexture; // R8UI — trail owner per tile + paletteTex: WebGLTexture; // RGBA32F — player colors + borderTex: WebGLTexture; // RGBA8 — border type + ember + defense + heatTexA: WebGLTexture; // R8 — fallout heat ping-pong A + heatTexB: WebGLTexture; // R8 — fallout heat ping-pong B +} + +export function createGPUResources( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + paletteTex: WebGLTexture, + borderTex: WebGLTexture, +): GPUResources { + const tileTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R16UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_SHORT, + data: null, + filter: gl.NEAREST, + }); + + const trailTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R8UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_BYTE, + data: null, + filter: gl.NEAREST, + }); + + const heatTexA = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R8, + format: gl.RED, + type: gl.UNSIGNED_BYTE, + data: null, + filter: gl.NEAREST, + }); + + const heatTexB = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R8, + format: gl.RED, + type: gl.UNSIGNED_BYTE, + data: null, + filter: gl.NEAREST, + }); + + return { tileTex, trailTex, paletteTex, borderTex, heatTexA, heatTexB }; +} + +export function disposeGPUResources( + gl: WebGL2RenderingContext, + res: GPUResources, +): void { + gl.deleteTexture(res.tileTex); + gl.deleteTexture(res.trailTex); + // paletteTex and borderTex are owned by renderer and BorderComputePass respectively + gl.deleteTexture(res.heatTexA); + gl.deleteTexture(res.heatTexB); +} diff --git a/src/client/render/gl/utils/heat-manager.ts b/src/client/render/gl/utils/heat-manager.ts new file mode 100644 index 0000000000..0a86230a5e --- /dev/null +++ b/src/client/render/gl/utils/heat-manager.ts @@ -0,0 +1,319 @@ +/** + * HeatManager — GPU-side fallout heat decay and transition detection. + * + * Extracted from FalloutBloomPass. Owns the heat ping-pong textures, the + * previous-tile-state snapshot, and the combined transition+decay shader. + * + * Used by both FalloutBloomPass (bloom extract reads heat) and LightmapPass + * (fallout light reads heat). Shared heat textures come from GPUResources. + */ + +import type { RenderSettings } from "../render-settings"; +import { + createFullscreenQuad, + createProgram, + createTexture2D, + shaderSrc, +} from "./gl-utils"; +import { FALLOUT_BIT, TILE_DEFINES } from "./tile-codec"; + +import heatDecayFragSrc from "../shaders/fallout-bloom/heat-decay.frag.glsl?raw"; +import fullscreenNoUvVertSrc from "../shaders/shared/fullscreen-no-uv.vert.glsl?raw"; + +export class HeatManager { + private gl: WebGL2RenderingContext; + private settings: RenderSettings; + private mapW: number; + private mapH: number; + private tileTex: WebGLTexture; + + // Heat ping-pong (R8, per-tile: 255=fresh, decays toward 0) + private heatTexA: WebGLTexture; + private heatTexB: WebGLTexture; + private heatFboA: WebGLFramebuffer; + private heatFboB: WebGLFramebuffer; + /** 0 = read A / write B, 1 = read B / write A */ + private heatCurrent = 0; + + // Previous tile state (R16UI) — GPU-side snapshot for transition detection + private prevTileTex: WebGLTexture; + private prevTileFbo: WebGLFramebuffer; + private tileTexReadFbo: WebGLFramebuffer; + /** True on first frame and after seek — blit tileTex→prevTileTex without transitions. */ + private needsPrevTileCopy = true; + + // Pending CPU → GPU writes + private pendingDecay = 0; + private pendingFullHeat: Uint8Array | null = null; + /** + * True when heat may be non-zero anywhere — gates the decay pass. + * Set true on each game tick (shader may detect new fallout transitions). + * Set false once accumulated decay since last activation exceeds 255 (fully drained). + */ + private heatActive = false; + /** Accumulated decay since heatActive was last set true. */ + private decayAccumulated = 0; + + // Decay program + private decayProg: WebGLProgram; + private uDecayMapSize: WebGLUniformLocation; + private uDecayAmount: WebGLUniformLocation; + + // Geometry + private quadVao: WebGLVertexArrayObject; + + constructor( + gl: WebGL2RenderingContext, + mapW: number, + mapH: number, + tileTex: WebGLTexture, + heatTexA: WebGLTexture, + heatTexB: WebGLTexture, + settings: RenderSettings, + ) { + this.gl = gl; + this.settings = settings; + this.mapW = mapW; + this.mapH = mapH; + this.tileTex = tileTex; + this.heatTexA = heatTexA; + this.heatTexB = heatTexB; + + this.heatFboA = this.createFboFor(heatTexA); + this.heatFboB = this.createFboFor(heatTexB); + + // Previous tile state texture (R16UI, for GPU transition detection) + this.prevTileTex = createTexture2D(gl, { + width: mapW, + height: mapH, + internalFormat: gl.R16UI, + format: gl.RED_INTEGER, + type: gl.UNSIGNED_SHORT, + data: null, + filter: gl.NEAREST, + }); + this.prevTileFbo = this.createFboFor(this.prevTileTex); + this.tileTexReadFbo = this.createFboFor(tileTex); + + // Decay program (tile-space, combined transition + decay) + this.decayProg = createProgram( + gl, + fullscreenNoUvVertSrc, + shaderSrc(heatDecayFragSrc, TILE_DEFINES), + ); + this.uDecayMapSize = gl.getUniformLocation(this.decayProg, "uMapSize")!; + this.uDecayAmount = gl.getUniformLocation(this.decayProg, "uDecay")!; + gl.useProgram(this.decayProg); + gl.uniform1i(gl.getUniformLocation(this.decayProg, "uHeatTex"), 0); + gl.uniform1i(gl.getUniformLocation(this.decayProg, "uTileTex"), 1); + gl.uniform1i(gl.getUniformLocation(this.decayProg, "uPrevTileTex"), 2); + + this.quadVao = createFullscreenQuad(gl); + } + + private createFboFor(tex: WebGLTexture): WebGLFramebuffer { + const gl = this.gl; + const fbo = gl.createFramebuffer()!; + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + tex, + 0, + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return fbo; + } + + /** Current heat read texture. */ + private get heatReadTex(): WebGLTexture { + return this.heatCurrent === 0 ? this.heatTexA : this.heatTexB; + } + private get heatWriteFbo(): WebGLFramebuffer { + return this.heatCurrent === 0 ? this.heatFboB : this.heatFboA; + } + private swapHeat(): void { + this.heatCurrent = 1 - this.heatCurrent; + } + + // --------------------------------------------------------------------------- + // Public API + // --------------------------------------------------------------------------- + + /** Current heat texture for reading (bloom extract and lightmap). */ + getHeatTex(): WebGLTexture { + return this.heatReadTex; + } + + /** + * Run GPU heat update: detect fallout-bit transitions, apply decay, + * then snapshot tileTex → prevTileTex. + * + * Call once per frame after tile texture is flushed to GPU. + */ + updateHeat(): void { + const gl = this.gl; + const mw = this.mapW; + const mh = this.mapH; + + // 1. Upload reconstructed heat on seek + if (this.pendingFullHeat) { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.heatReadTex); + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + mw, + mh, + gl.RED, + gl.UNSIGNED_BYTE, + this.pendingFullHeat, + ); + this.pendingFullHeat = null; + } + + // 2. First frame / seek: copy tileTex → prevTileTex, skip transitions + if (this.needsPrevTileCopy) { + this.blitTileToPrev(); + this.needsPrevTileCopy = false; + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return; + } + + // 3. Skip decay pass when nothing to do — no pending decay and heat already settled. + // Still blit tileTex→prevTileTex when a tick fired (pendingDecay > 0) so transition + // detection stays accurate if heat activates later. + if (!this.heatActive && this.pendingDecay === 0) return; + if (!this.heatActive) { + // Tick fired but no heat — just keep prevTileTex in sync and bail. + this.blitTileToPrev(); + this.pendingDecay = 0; + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return; + } + + // 4. Combined transition detection + decay (GPU ping-pong) + gl.bindFramebuffer(gl.FRAMEBUFFER, this.heatWriteFbo); + gl.viewport(0, 0, mw, mh); + gl.disable(gl.BLEND); + + gl.useProgram(this.decayProg); + gl.uniform2f(this.uDecayMapSize, mw, mh); + gl.uniform1f(this.uDecayAmount, this.pendingDecay); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.heatReadTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, this.tileTex); + gl.activeTexture(gl.TEXTURE2); + gl.bindTexture(gl.TEXTURE_2D, this.prevTileTex); + gl.bindVertexArray(this.quadVao); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + this.swapHeat(); + this.decayAccumulated += this.pendingDecay; + if (this.decayAccumulated >= 255) this.heatActive = false; + this.pendingDecay = 0; + + // 5. Snapshot current tileTex → prevTileTex for next frame + this.blitTileToPrev(); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + /** GPU blit: tileTex → prevTileTex (R16UI, NEAREST). */ + private blitTileToPrev(): void { + const gl = this.gl; + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.tileTexReadFbo); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.prevTileFbo); + gl.blitFramebuffer( + 0, + 0, + this.mapW, + this.mapH, + 0, + 0, + this.mapW, + this.mapH, + gl.COLOR_BUFFER_BIT, + gl.NEAREST, + ); + } + + /** + * Reset heat state on seek. Reconstructs heat from nuke history and + * masks out recaptured tiles. + */ + resetForSeek( + tileState: Uint16Array, + nukeEvents?: Array<{ tick: number; tiles: number[] }>, + currentTick?: number, + ): void { + let hasHeat = false; + if (nukeEvents && nukeEvents.length > 0 && currentTick !== undefined) { + const heat = this.reconstructHeat(nukeEvents, currentTick); + this.maskHeat(heat, tileState); + this.pendingFullHeat = heat; + hasHeat = heat.some((v) => v > 0); + } else { + this.pendingFullHeat = new Uint8Array(this.mapW * this.mapH); + } + this.pendingDecay = 0; + this.decayAccumulated = 0; + this.heatActive = hasHeat; + this.needsPrevTileCopy = true; + } + + /** Accumulate heat decay for one game tick. */ + decayHeat(): void { + this.pendingDecay += this.settings.falloutBloom.heatDecayPerTick; + // A tick fired — the shader may detect new fallout transitions, so heat is potentially active. + if (!this.heatActive) { + this.heatActive = true; + this.decayAccumulated = 0; + } + } + + // --------------------------------------------------------------------------- + // Internals + // --------------------------------------------------------------------------- + + private reconstructHeat( + nukeEvents: Array<{ tick: number; tiles: number[] }>, + currentTick: number, + ): Uint8Array { + const heat = new Uint8Array(this.mapW * this.mapH); + const decay = this.settings.falloutBloom.heatDecayPerTick; + for (const evt of nukeEvents) { + if (evt.tick > currentTick) continue; + const elapsed = currentTick - evt.tick; + const h = Math.round(255 - elapsed * decay); + if (h <= 0) continue; + for (const ref of evt.tiles) { + if (heat[ref] < h) heat[ref] = h; + } + } + return heat; + } + + private maskHeat(heat: Uint8Array, tileState: Uint16Array): void { + for (let i = 0; i < heat.length; i++) { + if (heat[i] > 0 && (tileState[i] & FALLOUT_BIT) === 0) { + heat[i] = 0; + } + } + } + + dispose(): void { + const gl = this.gl; + gl.deleteProgram(this.decayProg); + gl.deleteFramebuffer(this.heatFboA); + gl.deleteFramebuffer(this.heatFboB); + gl.deleteFramebuffer(this.prevTileFbo); + gl.deleteFramebuffer(this.tileTexReadFbo); + gl.deleteTexture(this.prevTileTex); + gl.deleteVertexArray(this.quadVao); + } +} diff --git a/src/client/render/gl/utils/nuke-trajectory.ts b/src/client/render/gl/utils/nuke-trajectory.ts new file mode 100644 index 0000000000..d020a65429 --- /dev/null +++ b/src/client/render/gl/utils/nuke-trajectory.ts @@ -0,0 +1,261 @@ +/** + * Nuke trajectory computation — Bezier control points and color thresholds. + * + * Matches upstream PathFinder.Parabola.ts + Line.ts math exactly. + * Pure functions, no game dependencies. + */ + +import type { NukeTrajectoryData } from "../../types"; + +// Upstream constants +const PARABOLA_MIN_HEIGHT = 50; +const TARGETABLE_RANGE = 150; +const TARGETABLE_RANGE_SQ = TARGETABLE_RANGE * TARGETABLE_RANGE; +const THRESHOLD_SAMPLES = 64; + +// SAM range formula: 150 - 480 / (level + 5) +const MAX_SAM_RANGE = 150; +const SAM_RANGE_DIVISOR = 480; +const SAM_RANGE_OFFSET = 5; + +export function samRange(level: number): number { + return MAX_SAM_RANGE - SAM_RANGE_DIVISOR / (level + SAM_RANGE_OFFSET); +} + +export interface SAMInfo { + x: number; + y: number; + rangeSq: number; +} + +/** Cubic Bezier evaluation at parameter t. */ +function bezier( + t: number, + p0: number, + p1: number, + p2: number, + p3: number, +): number { + const T = 1 - t; + return ( + T * T * T * p0 + 3 * T * T * t * p1 + 3 * T * t * t * p2 + t * t * t * p3 + ); +} + +function clamp(v: number, lo: number, hi: number): number { + return v < lo ? lo : v > hi ? hi : v; +} + +function distSq(ax: number, ay: number, bx: number, by: number): number { + const dx = ax - bx; + const dy = ay - by; + return dx * dx + dy * dy; +} + +/** + * Compute Bezier control points matching upstream parabola pathfinder. + * + * The curve bows perpendicular to the src→dst line. `directionUp` controls + * which side (in Y) the arc bows toward (upstream convention: true = -Y). + */ +export function computeNukeControlPoints( + srcX: number, + srcY: number, + dstX: number, + dstY: number, + mapH: number, + directionUp: boolean, +): { + p0x: number; + p0y: number; + p1x: number; + p1y: number; + p2x: number; + p2y: number; + p3x: number; + p3y: number; +} { + const dx = dstX - srcX; + const dy = dstY - srcY; + const dist = Math.sqrt(dx * dx + dy * dy); + const maxHeight = Math.max(dist / 3, PARABOLA_MIN_HEIGHT); + const hm = directionUp ? -1 : 1; + + return { + p0x: srcX, + p0y: srcY, + p1x: srcX + dx / 4, + p1y: clamp(srcY + dy / 4 + hm * maxHeight, 0, mapH - 1), + p2x: srcX + (dx * 3) / 4, + p2y: clamp(srcY + (dy * 3) / 4 + hm * maxHeight, 0, mapH - 1), + p3x: dstX, + p3y: dstY, + }; +} + +/** Binary-search for the exact t where distSq to (cx,cy) crosses rangeSq. */ +function refineCrossing( + cp: { + p0x: number; + p0y: number; + p1x: number; + p1y: number; + p2x: number; + p2y: number; + p3x: number; + p3y: number; + }, + cx: number, + cy: number, + rangeSq: number, + tLo: number, + tHi: number, + exitingRange: boolean, +): number { + for (let i = 0; i < 10; i++) { + const tMid = (tLo + tHi) * 0.5; + const x = bezier(tMid, cp.p0x, cp.p1x, cp.p2x, cp.p3x); + const y = bezier(tMid, cp.p0y, cp.p1y, cp.p2y, cp.p3y); + const inside = distSq(x, y, cx, cy) <= rangeSq; + if (exitingRange ? inside : !inside) tLo = tMid; + else tHi = tMid; + } + return (tLo + tHi) * 0.5; +} + +/** + * Sample the Bezier curve at regular t intervals and find color threshold + * t-values for untargetable zones and SAM intercept. + * + * Uses binary search refinement for sub-sample precision so that zone + * boundary markers don't jiggle when the cursor moves. + */ +export function computeTrajectoryThresholds( + cp: { + p0x: number; + p0y: number; + p1x: number; + p1y: number; + p2x: number; + p2y: number; + p3x: number; + p3y: number; + }, + srcX: number, + srcY: number, + dstX: number, + dstY: number, + sams: readonly SAMInfo[], +): { + tUntargetableStart: number; + tUntargetableEnd: number; + tSamIntercept: number; +} { + let tUntargetableStart = -1; + let tUntargetableEnd = -1; + let tSamIntercept = 1.0; + + const dt = 1.0 / THRESHOLD_SAMPLES; + + // Pass 1: find untargetable zone boundaries + for (let i = 1; i <= THRESHOLD_SAMPLES; i++) { + const t = i * dt; + const x = bezier(t, cp.p0x, cp.p1x, cp.p2x, cp.p3x); + const y = bezier(t, cp.p0y, cp.p1y, cp.p2y, cp.p3y); + + if (tUntargetableStart < 0) { + // Looking for first point outside source range + if (distSq(x, y, srcX, srcY) > TARGETABLE_RANGE_SQ) { + if (distSq(x, y, dstX, dstY) < TARGETABLE_RANGE_SQ) { + // Overlapping source & target range — no untargetable zone + break; + } + tUntargetableStart = refineCrossing( + cp, + srcX, + srcY, + TARGETABLE_RANGE_SQ, + t - dt, + t, + true, + ); + } + } else { + // Looking for first point inside target range + if (distSq(x, y, dstX, dstY) < TARGETABLE_RANGE_SQ) { + tUntargetableEnd = refineCrossing( + cp, + dstX, + dstY, + TARGETABLE_RANGE_SQ, + t - dt, + t, + false, + ); + break; + } + } + } + + // Pass 2: find SAM intercept (skip untargetable zone) + if (sams.length > 0) { + for (let i = 1; i <= THRESHOLD_SAMPLES; i++) { + const t = i * dt; + + // Skip untargetable segment + if ( + tUntargetableStart >= 0 && + t >= tUntargetableStart && + t <= tUntargetableEnd + ) { + continue; + } + + const x = bezier(t, cp.p0x, cp.p1x, cp.p2x, cp.p3x); + const y = bezier(t, cp.p0y, cp.p1y, cp.p2y, cp.p3y); + + for (const sam of sams) { + if (distSq(x, y, sam.x, sam.y) <= sam.rangeSq) { + tSamIntercept = refineCrossing( + cp, + sam.x, + sam.y, + sam.rangeSq, + t - dt, + t, + false, + ); + break; + } + } + if (tSamIntercept < 1.0) break; + } + } + + return { tUntargetableStart, tUntargetableEnd, tSamIntercept }; +} + +/** + * Build complete NukeTrajectoryData from source/target positions. + * Convenience function combining control point + threshold computation. + */ +export function buildNukeTrajectory( + srcX: number, + srcY: number, + dstX: number, + dstY: number, + mapH: number, + directionUp: boolean, + sams: readonly SAMInfo[], +): NukeTrajectoryData { + const cp = computeNukeControlPoints( + srcX, + srcY, + dstX, + dstY, + mapH, + directionUp, + ); + const th = computeTrajectoryThresholds(cp, srcX, srcY, dstX, dstY, sams); + return { ...cp, ...th }; +} diff --git a/src/client/render/gl/utils/tile-codec.ts b/src/client/render/gl/utils/tile-codec.ts new file mode 100644 index 0000000000..227016a4c9 --- /dev/null +++ b/src/client/render/gl/utils/tile-codec.ts @@ -0,0 +1,18 @@ +/** + * Tile state bit layout (R16UI). Single source of truth for TypeScript + GLSL. + * + * Bits 0-11: ownerID (player smallID, 0 = unowned) + * Bit 13: fallout + * Bit 14: defense bonus + */ + +export const OWNER_MASK = 0xfff; // bits 0-11 +export const FALLOUT_BIT = 1 << 13; // bit 13 +export const DEFENSE_BIT = 1 << 14; // bit 14 + +/** GLSL #define values for shaderSrc() injection. Bit indices, not masks. */ +export const TILE_DEFINES = { + OWNER_MASK: 0xfff, // used as uint(OWNER_MASK) in GLSL + FALLOUT_BIT: 13, // used as (1u << FALLOUT_BIT) in GLSL + DEFENSE_BIT: 14, // used as (1u << DEFENSE_BIT) in GLSL +}; diff --git a/src/client/render/gl/vite-env.d.ts b/src/client/render/gl/vite-env.d.ts new file mode 100644 index 0000000000..04933cc420 --- /dev/null +++ b/src/client/render/gl/vite-env.d.ts @@ -0,0 +1,8 @@ +declare module "*.glsl?raw" { + const src: string; + export default src; +} +declare module "*.png?url" { + const url: string; + export default url; +} diff --git a/src/client/render/types/frame-data.ts b/src/client/render/types/frame-data.ts new file mode 100644 index 0000000000..5e2574e60a --- /dev/null +++ b/src/client/render/types/frame-data.ts @@ -0,0 +1,76 @@ +import type { FrameEvents } from "./frame-events"; +import type { + AttackRingInput, + NameEntry, + NukeTelegraphData, + PlayerState, + PlayerStatusData, + TilePair, + UnitState, +} from "./renderer"; + +/** + * FrameData — the boundary contract between game integration and features. + * + * Produced once per frame by a driver (shim for live, codec for replay). + * All feature consumers (renderer, minimap, stats) read from this interface. + * They never touch game internals directly. + */ +export interface FrameData { + // ── Core accumulated state ──────────────────────────────────────────── + + readonly tick: number; + /** True during spawn phase (before gameplay begins). Always false for replay. */ + readonly inSpawnPhase: boolean; + readonly tileState: Uint16Array; + readonly trailState: Uint8Array; + readonly railroadState: Uint8Array; + readonly units: ReadonlyMap; + readonly players: ReadonlyMap; + readonly names: ReadonlyMap; + + // ── Per-frame events ────────────────────────────────────────────────── + + /** Everything that happened this frame — rendering FX and stats events. */ + readonly events: FrameEvents; + + // ── Upload hints ────────────────────────────────────────────────────── + + /** + * Changed tiles this frame for delta uploads. + * - `null` or `undefined` → full upload needed (live mode or keyframe seek) + * - array → delta upload (replay sequential advance) + */ + readonly changedTiles?: TilePair[] | null; + readonly railroadDirty: boolean; + readonly revealedRailTiles: number[]; + + /** + * Trail dirty row range for partial GPU upload. + * - `dirtyRowMin > dirtyRowMax` → no trail changes (skip upload) + * - Otherwise → upload rows [min, max] from trailState + * Only meaningful in `tileMode: "live"`. + */ + readonly trailDirtyRowMin: number; + readonly trailDirtyRowMax: number; + + // ── Derived (computed once by producer) ──────────────────────────────── + + readonly playerStatus: ReadonlyMap; + readonly relationMatrix: Uint8Array; + readonly relationSize: number; + readonly allianceClusters: ReadonlyMap; + readonly nukeTelegraphs: NukeTelegraphData[]; + readonly attackRings: AttackRingInput[]; + /** True when structures changed this tick (added/removed/level change). */ + readonly structuresDirty: boolean; + + // ── Upload semantics ────────────────────────────────────────────────── + + /** + * How tile data should reach the GPU: + * - `"live"` — arrays are mutated in-place by shim each tick (zero-copy refs) + * - `"copy"` — arrays may be swapped/reconstructed (renderer must copy) + */ + readonly tileMode: "live" | "copy"; +} diff --git a/src/client/render/types/frame-events.ts b/src/client/render/types/frame-events.ts new file mode 100644 index 0000000000..ae5c9c214e --- /dev/null +++ b/src/client/render/types/frame-events.ts @@ -0,0 +1,114 @@ +import type { + ConquestFx, + DeadUnitFx, + PlayerState, + UnitState, +} from "./renderer"; + +// ── Supporting event types ────────────────────────────────────────────── + +export interface AllianceFormedEvent { + requestorID: number; + recipientID: number; +} + +export interface AllianceBrokenEvent { + traitorID: number; + betrayedID: number; +} + +export interface AllianceExpiredEvent { + player1ID: number; + player2ID: number; +} + +export interface EmbargoEvent { + type: "start" | "stop"; + playerID: number; + embargoedID: number; +} + +export interface TargetEvent { + playerID: number; + targetID: number; +} + +export interface BonusEvent { + playerID: string; + smallID: number; + tile: number; + gold: number; + troops: number; +} + +export interface NukeIncomingEvent { + playerID: number; +} + +export interface EmojiEvent { + senderID: number; + message: string; +} + +export interface DisplayMessageEvent { + messageType: number; + playerID: number | null; + goldAmount?: number; + params?: Record; +} + +export interface WinEvent { + /** Tuple: ["player", ...playerIds] or ["team"|"nation", name, ...playerIds] */ + winner: string[]; +} + +// ── Empty events constant ─────────────────────────────────────────────── + +/** Shared empty-events object. Safe to reuse — all arrays are empty and never mutated. */ +export const EMPTY_FRAME_EVENTS: FrameEvents = { + deadUnits: [], + conquestEvents: [], + unitUpdates: [], + playerUpdates: [], + allianceFormed: [], + allianceBroken: [], + allianceExpired: [], + embargoEvents: [], + targetEvents: [], + bonusEvents: [], + nukeIncoming: [], + emojis: [], + displayMessages: [], + wins: [], + gamePaused: null, +}; + +// ── FrameEvents ───────────────────────────────────────────────────────── + +/** + * Everything that happened THIS frame. Accumulated state and derived data + * live on FrameData directly — per-frame ephemeral events live here. + * + * Empty arrays when nothing happened. Producers must always populate every + * field (no undefined — consumers shouldn't need null checks). + */ +export interface FrameEvents { + // Rendering events + readonly deadUnits: DeadUnitFx[]; + readonly conquestEvents: ConquestFx[]; + + // Stats events + readonly unitUpdates: UnitState[]; + readonly playerUpdates: PlayerState[]; + readonly allianceFormed: AllianceFormedEvent[]; + readonly allianceBroken: AllianceBrokenEvent[]; + readonly allianceExpired: AllianceExpiredEvent[]; + readonly embargoEvents: EmbargoEvent[]; + readonly targetEvents: TargetEvent[]; + readonly bonusEvents: BonusEvent[]; + readonly nukeIncoming: NukeIncomingEvent[]; + readonly emojis: EmojiEvent[]; + readonly displayMessages: DisplayMessageEvent[]; + readonly wins: WinEvent[]; + readonly gamePaused: boolean | null; +} diff --git a/src/client/render/types/frame-source.ts b/src/client/render/types/frame-source.ts new file mode 100644 index 0000000000..74f6f0217c --- /dev/null +++ b/src/client/render/types/frame-source.ts @@ -0,0 +1,38 @@ +import type { FrameData } from "./frame-data"; +import type { PlayerStatic } from "./renderer"; + +/** + * Static per-session metadata. Set once at game-start, never changes. + */ +export interface GameStartConfig { + gameID: string; + mapWidth: number; + mapHeight: number; + /** 0 for spectator/replay. */ + localPlayerSmallID: number; + players: PlayerStatic[]; + gameMode?: string; + difficulty?: string; + numLandTiles?: number; +} + +/** + * Mode-agnostic frame source. Features subscribe here and don't care + * whether data comes from a live game or a replay file. + * + * All subscription methods return an unsubscribe function. + * + * Late-join: `onGameStart` fires immediately with cached config if + * subscribed after game-start. `onFrame` does NOT late-fire — subscriber + * waits for the next real tick. + * + * Game-end: `onGameEnd` fires on win detection. `onFrame` continues + * emitting — the simulation runs past game-end. + */ +export interface FrameSource { + onFrame(handler: (frame: FrameData) => void): () => void; + onGameStart(handler: (config: GameStartConfig) => void): () => void; + onGameEnd(handler: () => void): () => void; + /** null before game-start. Stays valid after game-end (same session). */ + readonly config: GameStartConfig | null; +} diff --git a/src/client/render/types/game-updates.ts b/src/client/render/types/game-updates.ts new file mode 100644 index 0000000000..40bae1a5d2 --- /dev/null +++ b/src/client/render/types/game-updates.ts @@ -0,0 +1,171 @@ +/** + * Game update type constants and typed event payloads. + * + * Shared contract between shim (live game) and codec (replay). + * Values must match the LIVE deployed game's GameUpdates.ts. + */ + +// --------------------------------------------------------------------------- +// GameUpdateType constants +// --------------------------------------------------------------------------- + +export const GameUpdateType = { + Tile: 0, + Unit: 1, + Player: 2, + DisplayEvent: 3, + DisplayChatEvent: 4, + AllianceRequest: 5, + AllianceRequestReply: 6, + BrokeAlliance: 7, + AllianceExpired: 8, + AllianceExtension: 9, + TargetPlayer: 10, + Emoji: 11, + Win: 12, + Hash: 13, + UnitIncoming: 14, + BonusEvent: 15, + RailroadDestructionEvent: 16, + RailroadConstructionEvent: 17, + RailroadSnapEvent: 18, + ConquestEvent: 19, + EmbargoEvent: 20, + GamePaused: 21, + NukeDetonation: 22, +} as const; + +/** MessageType enum values from the game source. */ +export const MessageType = { + SAM_HIT: 9, + SENT_GOLD_TO_PLAYER: 18, + RECEIVED_GOLD_FROM_PLAYER: 19, + RECEIVED_GOLD_FROM_TRADE: 20, + SENT_TROOPS_TO_PLAYER: 21, + RECEIVED_TROOPS_FROM_PLAYER: 22, +} as const; + +// --------------------------------------------------------------------------- +// Typed update payloads (keyed by GameUpdateType values) +// --------------------------------------------------------------------------- + +export type PlayerType = "HUMAN" | "NATION" | "BOT"; + +export interface UnitEventUpdate { + id: number; + unitType: string; + ownerID: number; + pos: number; + lastPos?: number; + isActive: boolean; + level: number; + underConstruction?: boolean; + markedForDeletion: number | false; + lastOwnerID?: number; + trainType?: string; + loaded?: boolean; + targetUnitId?: number; + targetTile?: number; + health?: number; + troops?: number; + reachedTarget?: boolean; + retreating?: boolean; + targetable?: boolean; + hasTrainStation?: boolean; + missileTimerQueue?: number[]; +} + +export interface PlayerEventUpdate { + id: string; + clientID?: string | null; + smallID: number; + displayName: string; + playerType: PlayerType; + team?: string | null; + isAlive: boolean; + troops: number; + gold: bigint; + tilesOwned: number; + outgoingAttacks?: AttackEventUpdate[]; + incomingAttacks?: AttackEventUpdate[]; + allies?: number[]; + betrayals?: number; +} + +export interface AttackEventUpdate { + troops: number; +} + +export interface WinUpdate { + /** Winner tuple: ["player", ...playerIds] or ["team"|"nation", name, ...playerIds] */ + winner?: [string, ...string[]]; +} + +export interface AllianceReplyUpdate { + accepted: boolean; + request?: { requestorID: number; recipientID: number }; +} + +export interface BrokeAllianceUpdate { + traitorID: number; + betrayedID: number; +} + +export interface AllianceExpiredUpdate { + player1ID: number; + player2ID: number; +} + +export interface EmbargoUpdate { + event: "start" | "stop"; + playerID: number; + embargoedID: number; +} + +export interface TargetPlayerUpdate { + playerID: number; + targetID: number; +} + +export interface BonusUpdate { + player: string; + tile?: number; + gold: number; + troops: number; +} + +export interface UnitIncomingUpdate { + playerID: number; +} + +export interface EmojiUpdate { + emoji?: { senderID: number; message: string }; +} + +export interface DisplayMessageUpdate { + messageType: number; + playerID: number | null; + goldAmount?: bigint | number; + params?: Record; +} + +export interface GamePausedUpdate { + paused: boolean; +} + +export interface RailroadConstructionUpdate { + id: number; + tiles: number[]; +} + +export interface RailroadDestructionUpdate { + id: number; +} + +export interface RailroadSnapUpdate { + originalId: number; + newId1: number; + newId2: number; + tiles1: number[]; + tiles2: number[]; +} diff --git a/src/client/render/types/game.ts b/src/client/render/types/game.ts new file mode 100644 index 0000000000..5a6809240f --- /dev/null +++ b/src/client/render/types/game.ts @@ -0,0 +1,17 @@ +/** + * The frame data type that both the live game and encoder consume. + * This matches the GameUpdateViewData from the live game's update loop. + */ +export interface GameUpdateViewData { + tick: number; + updates: Record; + packedTileUpdates: unknown; + packedMotionPlans?: Uint32Array; + playerNameViewData: Record; +} + +/** + * Minimal GameStartInfo for the encoder's finish() call. + * The actual object is opaque JSON — we just need it to be serializable. + */ +export type GameStartInfo = Record; diff --git a/src/client/render/types/index.ts b/src/client/render/types/index.ts new file mode 100644 index 0000000000..8785374f34 --- /dev/null +++ b/src/client/render/types/index.ts @@ -0,0 +1,108 @@ +// Renderer types (units, players, tiles, names, config) +export { PlayerTypeEnum, TrainType } from "./renderer"; +export type { + AllianceData, + AttackData, + AttackRingInput, + ConquestFx, + DeadUnitFx, + EmojiData, + GhostPreviewData, + NameEntry, + NukeTelegraphData, + NukeTrajectoryData, + PlayerState, + PlayerStatic, + PlayerStatusData, + RendererConfig, + TilePair, + UnitState, +} from "./renderer"; + +// Frame data — boundary contract between game integration and features +export type { FrameData } from "./frame-data"; + +// Frame events — per-frame ephemeral events (rendering FX + stats events) +export { EMPTY_FRAME_EVENTS } from "./frame-events"; +export type { + AllianceBrokenEvent, + AllianceExpiredEvent, + AllianceFormedEvent, + BonusEvent, + DisplayMessageEvent, + EmbargoEvent, + EmojiEvent, + FrameEvents, + NukeIncomingEvent, + TargetEvent, + WinEvent, +} from "./frame-events"; + +// Frame source — mode-agnostic subscription interface +export type { FrameSource, GameStartConfig } from "./frame-source"; + +// Game update types +export type { GameStartInfo, GameUpdateViewData } from "./game"; + +// Replay types (header, frames, codec helpers) +export type { + ChunkIndexEntry, + FrameSnapshot, + GridPlanRecord, + GzipFn, + InflateFn, + MotionPlanRecord, + RawDelta, + RawFrame, + RawKeyframe, + ReplayHeader, + StreamableReplayInfo, + TrainPlanRecord, +} from "./replay"; + +// Game update type constants and event payloads (shared between shim + codec) +export { GameUpdateType, MessageType } from "./game-updates"; +export type { + AllianceExpiredUpdate, + AllianceReplyUpdate, + AttackEventUpdate, + BonusUpdate, + BrokeAllianceUpdate, + DisplayMessageUpdate, + EmbargoUpdate, + EmojiUpdate, + GamePausedUpdate, + PlayerEventUpdate, + PlayerType, + RailroadConstructionUpdate, + RailroadDestructionUpdate, + RailroadSnapUpdate, + TargetPlayerUpdate, + UnitEventUpdate, + UnitIncomingUpdate, + WinUpdate, +} from "./game-updates"; + +// Unit type string constants and derived sets +export { + ALL_UNIT_TYPES, + NUKE_MAGNITUDES, + NUKE_TYPES, + STRUCTURE_TYPES, + UT_ATOM_BOMB, + UT_CITY, + UT_DEFENSE_POST, + UT_FACTORY, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_MIRV_WARHEAD, + UT_MISSILE_SILO, + UT_PORT, + UT_SAM_LAUNCHER, + UT_SAM_MISSILE, + UT_SHELL, + UT_TRADE_SHIP, + UT_TRAIN, + UT_TRANSPORT, + UT_WARSHIP, +} from "./unit-type"; diff --git a/src/client/render/types/renderer.ts b/src/client/render/types/renderer.ts new file mode 100644 index 0000000000..14b873207f --- /dev/null +++ b/src/client/render/types/renderer.ts @@ -0,0 +1,208 @@ +/** TrainType enum — numeric values matching UnitState.trainType. */ +export enum TrainType { + Engine = 0, + TailEngine = 1, + Carriage = 2, +} + +/** Numeric player type — matching PlayerStatic.playerType. */ +export enum PlayerTypeEnum { + Human = 0, + Bot = 1, + Nation = 2, +} + +/** Static player data from the header dictionary */ +export interface PlayerStatic { + smallID: number; + id: string; + name: string; + displayName: string; + clientID: string | null; + playerType: PlayerTypeEnum; + team: string | null; + isLobbyCreator: boolean; + flag?: string; + /** Hex color (e.g. "#ff0000"). Populated from territoryColor (live) or palette (replay). */ + color?: string; +} + +export interface AttackData { + attackerID: number; + targetID: number; + troops: number; + id: string; + retreating: boolean; +} + +export interface AllianceData { + id: number; + other: string; + createdAt: number; + expiresAt: number; + hasExtensionRequest: boolean; +} + +export interface EmojiData { + message: string; + senderID: number; + recipientID: number | "AllPlayers"; + createdAt: number; +} + +export interface PlayerState { + smallID: number; + isAlive: boolean; + isDisconnected: boolean; + tilesOwned: number; + gold: number; + troops: number; + isTraitor: boolean; + traitorRemainingTicks: number; + betrayals: number; + hasSpawned: boolean; + lastDeleteUnitTick: number; + allies: number[]; + embargoes: string[]; + targets: number[]; + outgoingAttacks: AttackData[]; + incomingAttacks: AttackData[]; + outgoingAllianceRequests: string[]; + alliances: AllianceData[]; + outgoingEmojis: EmojiData[]; +} + +export interface UnitState { + id: number; + unitType: string; + ownerID: number; + lastOwnerID: number | null; + pos: number; + lastPos: number; + isActive: boolean; + reachedTarget: boolean; + retreating: boolean; + targetable: boolean; + markedForDeletion: number | false; // -1 -> false, else tick + health: number | null; + underConstruction: boolean; + targetUnitId: number | null; + targetTile: number | null; + troops: number; + missileTimerQueue: number[]; + level: number; + hasTrainStation: boolean; + trainType: number | null; // 0=Engine, 1=TailEngine, 2=Carriage + loaded: boolean | null; + constructionStartTick: number | null; +} + +/** Minimal dead-unit data needed by the FX pass. */ +export interface DeadUnitFx { + unitType: string; + pos: number; + reachedTarget: boolean; + /** Ticks since the event occurred (0 = this frame, >0 = seeked past it). */ + tickAge?: number; +} + +/** Conquest event data for the gold popup + sword sprite FX. */ +export interface ConquestFx { + x: number; // world tile X (conquered player's name location) + y: number; // world tile Y + gold: number; // gold amount awarded + /** Ticks since the event occurred (0 = this frame, >0 = seeked past it). */ + tickAge?: number; +} + +export interface TilePair { + ref: number; + state: number; +} + +export interface NameEntry { + playerID: string; + x: number; + y: number; + size: number; +} + +/** Per-player status data for the GPU name/status-icon passes. */ +export interface PlayerStatusData { + crown: boolean; + traitor: boolean; + disconnected: boolean; + alliance: boolean; + allianceReq: boolean; + target: boolean; + embargo: boolean; + nukeActive: boolean; + nukeTargetsMe: boolean; + traitorRemainingTicks: number; + allianceFraction: number; +} + +/** Ghost structure preview data for build-mode visualization. */ +export interface GhostPreviewData { + ghostType: string; // UnitType string ("City", "Port", etc.) + tileX: number; // Hover tile X + tileY: number; // Hover tile Y + canBuild: boolean; // Valid placement? + canUpgrade: boolean; // Upgrading existing structure? + cost: number; // Gold cost + ghostRailPaths: number[][]; // TileRef paths (City/Port only) + overlappingRailroads: number[]; // Rail IDs in snap zone + ownerID: number; // Player's smallID (for color) + /** Tile position of existing structure being upgraded (null if fresh build). */ + upgradeTargetTile: number | null; + /** Range radius in tiles for the placement circle (0 = no circle). */ + rangeRadius: number; +} + +/** Nuke trajectory preview data — Bezier control points + color thresholds. */ +export interface NukeTrajectoryData { + /** Bezier control points (world-space tile coordinates). */ + p0x: number; + p0y: number; + p1x: number; + p1y: number; + p2x: number; + p2y: number; + p3x: number; + p3y: number; + /** t-value (0..1) where bomb leaves source's targetable range. -1 if ranges overlap. */ + tUntargetableStart: number; + /** t-value (0..1) where bomb enters target's targetable range. -1 if ranges overlap. */ + tUntargetableEnd: number; + /** t-value (0..1) of first SAM intercept point. 1.0 = no intercept. */ + tSamIntercept: number; +} + +/** Input data for attack ring visualization. */ +export interface AttackRingInput { + x: number; + y: number; + unitId: number; +} + +/** In-flight nuke target circle data. */ +export interface NukeTelegraphData { + x: number; + y: number; + innerRadius: number; + outerRadius: number; +} + +/** Lean config for constructing the GPU renderer — no replay-specific fields. */ +export interface RendererConfig { + mapWidth: number; + mapHeight: number; + unitTypes: string[]; + players: PlayerStatic[]; + /** + * Pre-allocated player capacity for GPU textures. + * Defaults to `players.length` when omitted. Set higher when players + * arrive after construction (e.g. bots are created on tick 1). + */ + maxPlayers?: number; +} diff --git a/src/client/render/types/replay.ts b/src/client/render/types/replay.ts new file mode 100644 index 0000000000..c02ccc2831 --- /dev/null +++ b/src/client/render/types/replay.ts @@ -0,0 +1,144 @@ +import type { + ConquestFx, + DeadUnitFx, + NameEntry, + PlayerState, + PlayerStatic, + RendererConfig, + TilePair, + UnitState, +} from "./renderer"; + +/** Chunk index entry — one per chunk in the file */ +export interface ChunkIndexEntry { + compressedOffset: number; + compressedSize: number; + decompressedSize: number; + frameCount: number; +} + +/** Subset of header available after streaming preamble (before full file download). */ +export interface StreamableReplayInfo extends RendererConfig { + totalFrames: number; + keyframeInterval: number; + numLandTiles: number; + gameStartInfo: unknown; + chunks: ChunkIndexEntry[]; +} + +/** Parsed v6 file header + dictionaries + chunk index + trailer sections */ +export interface ReplayHeader extends StreamableReplayInfo { + magic: number; + version: number; + gameID: string; + totalFrames: number; + keyframeInterval: number; + numLandTiles: number; + processedAt: number; + processingDurationMs: number; + gameStartInfo: unknown; + players: PlayerStatic[]; + /** Chunk index — per-chunk offsets and sizes */ + chunks: ChunkIndexEntry[]; + /** Nuke detonation events — top-level index for seek-time heat reconstruction */ + nukeEvents: Array<{ tick: number; tiles: number[] }>; + /** Railroad events — top-level index for seek-time railroad reconstruction */ + railroadEvents: Array<{ tick: number; type: number; data: unknown }>; + /** Motion plan events — top-level index for plan-driven unit positions and trails */ + motionPlanEvents: MotionPlanRecord[]; + /** Construction start events — top-level index for seek-time construction progress */ + constructionStarts: Array<{ unitId: number; startTick: number }>; + /** Conquest events — top-level index for seek-time gold popup + sword sprite */ + conquestEvents: Array<{ tick: number; x: number; y: number; gold: number }>; + /** Dead unit events — top-level index for seek-time explosion/death FX */ + deadUnitEvents: Array<{ + tick: number; + unitType: string; + pos: number; + reachedTarget: boolean; + }>; + /** Player elimination events — tick when each player's isAlive transitioned to false */ + eliminationEvents: Array<{ tick: number; smallID: number }>; +} + +/** Raw decoded v4 keyframe data — tile data is a raw Uint16Array blob */ +export interface RawKeyframe { + type: 0; + tick: number; + /** Raw tile blob: Uint16Array[mapWidth x mapHeight]. Direct GPU upload. */ + tileBlob: Uint16Array; + players: Map; + units: Map; + names: Map; + miscUpdates: Record | null; +} + +/** Raw decoded delta frame data */ +export interface RawDelta { + type: 1; + tick: number; + tiles: TilePair[]; + playerDeltas: Map; // new or changed players (full state after applying delta) + playersRemoved: number[]; + unitDeltas: Map; + unitsRemoved: number[]; + nameChanges: Map; + miscUpdates: Record | null; +} + +export type RawFrame = RawKeyframe | RawDelta; + +/** Full accumulated game state at a given tick */ +export interface FrameSnapshot { + tick: number; + players: Map; + units: Map; + names: Map; + /** Tiles changed in this frame only (for incremental rendering). null = full upload needed. */ + changedTiles: TilePair[] | null; + /** Units that died this frame (FX-only data). Empty on keyframes. */ + deadUnits: DeadUnitFx[]; + /** Conquest events active at this tick (from global index). */ + conquestEvents: ConquestFx[]; + /** Per-frame misc updates (alliances, donations, trades, etc.). null = none. */ + miscUpdates: Record | null; +} + +/** + * Inflate function type — platform provides its implementation. + * Node: zlib.inflateSync, Browser: pako.inflate + */ +export type InflateFn = (data: Uint8Array) => Uint8Array; + +/** + * Gzip function type — platform provides its implementation. + * Node: zlib.gzipSync, Browser: pako.gzip + */ +export type GzipFn = (data: Uint8Array) => Uint8Array | Promise; + +// --------------------------------------------------------------------------- +// Motion plan records — stored as a file-level index for plan-driven units +// (transport ships, trade ships, trains). +// --------------------------------------------------------------------------- + +export interface GridPlanRecord { + kind: "grid"; + unitId: number; + planId: number; + startTick: number; + ticksPerStep: number; + path: Uint32Array; +} + +export interface TrainPlanRecord { + kind: "train"; + engineUnitId: number; + carUnitIds: Uint32Array; + planId: number; + startTick: number; + speed: number; + spacing: number; + path: Uint32Array; +} + +export type MotionPlanRecord = GridPlanRecord | TrainPlanRecord; diff --git a/src/client/render/types/unit-type.ts b/src/client/render/types/unit-type.ts new file mode 100644 index 0000000000..70dd17efde --- /dev/null +++ b/src/client/render/types/unit-type.ts @@ -0,0 +1,83 @@ +/** + * Canonical unit type string constants. + * + * These match the strings the upstream game sends in UnitEventUpdate.unitType. + * Use these instead of raw string literals to prevent typos and enable + * find-all-references. + */ + +// --------------------------------------------------------------------------- +// Individual unit type constants +// --------------------------------------------------------------------------- + +// Mobile units +export const UT_TRANSPORT = "Transport" as const; +export const UT_TRADE_SHIP = "Trade Ship" as const; +export const UT_WARSHIP = "Warship" as const; +export const UT_ATOM_BOMB = "Atom Bomb" as const; +export const UT_HYDROGEN_BOMB = "Hydrogen Bomb" as const; +export const UT_MIRV = "MIRV" as const; +export const UT_SAM_MISSILE = "SAMMissile" as const; +export const UT_SHELL = "Shell" as const; +export const UT_MIRV_WARHEAD = "MIRV Warhead" as const; +export const UT_TRAIN = "Train" as const; + +// Structures +export const UT_CITY = "City" as const; +export const UT_PORT = "Port" as const; +export const UT_FACTORY = "Factory" as const; +export const UT_DEFENSE_POST = "Defense Post" as const; +export const UT_SAM_LAUNCHER = "SAM Launcher" as const; +export const UT_MISSILE_SILO = "Missile Silo" as const; + +// --------------------------------------------------------------------------- +// Derived sets +// --------------------------------------------------------------------------- + +export const STRUCTURE_TYPES: ReadonlySet = new Set([ + UT_CITY, + UT_PORT, + UT_FACTORY, + UT_DEFENSE_POST, + UT_SAM_LAUNCHER, + UT_MISSILE_SILO, +]); + +export const NUKE_TYPES: ReadonlySet = new Set([ + UT_ATOM_BOMB, + UT_HYDROGEN_BOMB, + UT_MIRV, +]); + +/** Blast radii (in tiles) matching upstream DefaultConfig.nukeMagnitudes(). */ +export const NUKE_MAGNITUDES: Readonly< + Record +> = { + [UT_ATOM_BOMB]: { inner: 12, outer: 30 }, + [UT_HYDROGEN_BOMB]: { inner: 80, outer: 100 }, + [UT_MIRV_WARHEAD]: { inner: 12, outer: 18 }, +}; + +// --------------------------------------------------------------------------- +// Ordered lists (atlas column order — used by GPU passes + header) +// --------------------------------------------------------------------------- + +/** All unit type strings in the canonical order used by RendererConfig.unitTypes. */ +export const ALL_UNIT_TYPES = [ + UT_TRANSPORT, + UT_TRADE_SHIP, + UT_WARSHIP, + UT_ATOM_BOMB, + UT_HYDROGEN_BOMB, + UT_MIRV, + UT_SAM_MISSILE, + UT_SHELL, + UT_MIRV_WARHEAD, + UT_CITY, + UT_PORT, + UT_FACTORY, + UT_DEFENSE_POST, + UT_SAM_LAUNCHER, + UT_MISSILE_SILO, + UT_TRAIN, +] as const; From 53cf2d43f81028203b56aaab5f209702292cce45 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 08:55:02 -0700 Subject: [PATCH 02/34] migrate away from canvas --- package-lock.json | 7 + package.json | 1 + src/client/ClientGameRunner.ts | 75 ++ src/client/WebGLFrameBuilder.ts | 247 ++++++ src/client/graphics/GameRenderer.ts | 33 +- src/client/graphics/TransformHandler.ts | 4 +- .../graphics/layers/CoordinateGridLayer.ts | 324 -------- src/client/graphics/layers/FxLayer.ts | 379 --------- .../layers/NukeTrajectoryPreviewLayer.ts | 428 ---------- src/client/graphics/layers/RailroadLayer.ts | 501 ------------ src/client/graphics/layers/RailroadSprites.ts | 161 ---- src/client/graphics/layers/RailroadView.ts | 176 ---- src/client/graphics/layers/SAMRadiusLayer.ts | 334 -------- .../graphics/layers/StructureIconsLayer.ts | 457 +---------- src/client/graphics/layers/StructureLayer.ts | 303 ------- src/client/graphics/layers/TerrainLayer.ts | 108 --- src/client/graphics/layers/TerritoryLayer.ts | 709 ---------------- src/client/graphics/layers/UILayer.ts | 192 +---- src/client/graphics/layers/UnitLayer.ts | 768 ------------------ src/core/game/GameView.ts | 3 + tests/client/graphics/UILayer.test.ts | 105 --- 21 files changed, 364 insertions(+), 4951 deletions(-) create mode 100644 src/client/WebGLFrameBuilder.ts delete mode 100644 src/client/graphics/layers/CoordinateGridLayer.ts delete mode 100644 src/client/graphics/layers/FxLayer.ts delete mode 100644 src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts delete mode 100644 src/client/graphics/layers/RailroadLayer.ts delete mode 100644 src/client/graphics/layers/RailroadSprites.ts delete mode 100644 src/client/graphics/layers/RailroadView.ts delete mode 100644 src/client/graphics/layers/SAMRadiusLayer.ts delete mode 100644 src/client/graphics/layers/StructureLayer.ts delete mode 100644 src/client/graphics/layers/TerrainLayer.ts delete mode 100644 src/client/graphics/layers/TerritoryLayer.ts delete mode 100644 src/client/graphics/layers/UnitLayer.ts diff --git a/package-lock.json b/package-lock.json index fd3d317738..7113dfa9d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "ip-anonymize": "^0.1.0", "jose": "^6.2.3", "js-yaml": "^4.1.1", + "lil-gui": "^0.21.0", "limiter": "^3.0.0", "nanoid": "^5.1.11", "node-html-parser": "^7.1.0", @@ -6689,6 +6690,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lil-gui": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.21.0.tgz", + "integrity": "sha512-tpvxN7v1GvE/Tv+GRopfOp0W7fVEjF4PltkuX8vOCIfim22rD1ztvfkoEMcv9lzQeuNUSeIrUmUjBwmlW/oUew==", + "license": "MIT" + }, "node_modules/limiter": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/limiter/-/limiter-3.0.0.tgz", diff --git a/package.json b/package.json index 3ed080276b..7b53252281 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "ip-anonymize": "^0.1.0", "jose": "^6.2.3", "js-yaml": "^4.1.1", + "lil-gui": "^0.21.0", "limiter": "^3.0.0", "nanoid": "^5.1.11", "node-html-parser": "^7.1.0", diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 5cb2ad853b..22bec1aff6 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -58,8 +58,11 @@ import { Transport, } from "./Transport"; import { createCanvas } from "./Utils"; +import { WebGLFrameBuilder } from "./WebGLFrameBuilder"; import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; import { GoToPlayerEvent } from "./graphics/TransformHandler"; +import { GameView as WebGLGameView } from "./render/gl"; +import { ALL_UNIT_TYPES } from "./render/types"; import { SoundManager } from "./sound/SoundManager"; export interface LobbyConfig { @@ -225,6 +228,68 @@ export function joinLobby( }; } +function mountWebGLDebugRenderer( + terrainMap: TerrainMapData, + gameView: GameView, + transformHandler: import("./graphics/TransformHandler").TransformHandler, +): { builder: WebGLFrameBuilder; syncCamera: () => void } { + const gameMap = terrainMap.gameMap; + const mapWidth = gameMap.width(); + const mapHeight = gameMap.height(); + + const terrainBytes = new Uint8Array(mapWidth * mapHeight); + for (let y = 0; y < mapHeight; y++) { + for (let x = 0; x < mapWidth; x++) { + terrainBytes[y * mapWidth + x] = gameMap.terrainByte(gameMap.ref(x, y)); + } + } + + const glCanvas = createCanvas(); + glCanvas.id = "webgl-debug-canvas"; + glCanvas.style.pointerEvents = "none"; + document.body.insertBefore(glCanvas, document.body.firstChild); + + const palette = new Float32Array(4096 * 2 * 4); + const view = new WebGLGameView( + glCanvas, + { + mapWidth, + mapHeight, + unitTypes: [...ALL_UNIT_TYPES], + players: [], + }, + terrainBytes, + palette, + ); + + window.addEventListener("keydown", (e) => { + if (e.key === "\\") { + glCanvas.style.display = + glCanvas.style.display === "none" ? "block" : "none"; + } + }); + + const syncCamera = (): void => { + const scale = transformHandler.scale; + const dpr = window.devicePixelRatio || 1; + const canvasW = glCanvas.clientWidth; + const canvasH = glCanvas.clientHeight; + const centerX = + transformHandler.offsetX + + mapWidth / 2 + + (canvasW - mapWidth) / (2 * scale); + const centerY = + transformHandler.offsetY + + mapHeight / 2 + + (canvasH - mapHeight) / (2 * scale); + view.setCameraState(centerX, centerY, scale * dpr); + }; + + (window as unknown as { __webglView?: unknown }).__webglView = view; + + return { builder: new WebGLFrameBuilder(view, gameView), syncCamera }; +} + async function createClientGame( lobbyConfig: LobbyConfig, clientID: ClientID | undefined, @@ -276,6 +341,13 @@ async function createClientGame( lobbyConfig.playerRole, ); + const { builder: webglBuilder, syncCamera } = mountWebGLDebugRenderer( + gameMap, + gameView, + gameRenderer.transformHandler, + ); + gameRenderer.onPreRender = syncCamera; + console.log( `creating private game got difficulty: ${lobbyConfig.gameStartInfo.config.difficulty}`, ); @@ -291,6 +363,7 @@ async function createClientGame( gameView, soundManager, userSettings, + webglBuilder, ); } catch (err) { soundManager.dispose(); @@ -323,6 +396,7 @@ export class ClientGameRunner { private gameView: GameView, private soundManager: SoundManager, private userSettings: UserSettings, + private webglBuilder: WebGLFrameBuilder | null = null, ) { this.lastMessageTime = Date.now(); } @@ -433,6 +507,7 @@ export class ClientGameRunner { this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash)); }); this.gameView.update(gu); + this.webglBuilder?.update(this.gameView, gu); this.renderer.tick(); // Emit tick metrics event for performance overlay diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts new file mode 100644 index 0000000000..be5a768199 --- /dev/null +++ b/src/client/WebGLFrameBuilder.ts @@ -0,0 +1,247 @@ +import { Colord } from "colord"; +import { PlayerType, TrainType, UnitType } from "../core/game/Game"; +import { GameUpdateType, GameUpdateViewData } from "../core/game/GameUpdates"; +import { GameView } from "../core/game/GameView"; +import { RailroadCache } from "./render/frame/railroad-cache"; +import { TrailManager } from "./render/frame/trail-manager"; +import { + PlayerStatic, + UnitState, + GameView as WebGLGameView, +} from "./render/gl"; +import { + BonusEvent, + ConquestFx, + DeadUnitFx, + PlayerTypeEnum, + TrainType as RendererTrainType, +} from "./render/types"; + +const TRAIL_TYPES: ReadonlySet = new Set([ + UnitType.TransportShip, + UnitType.AtomBomb, + UnitType.HydrogenBomb, + UnitType.MIRV, + UnitType.MIRVWarhead, +]); + +const PALETTE_SIZE = 4096; + +export class WebGLFrameBuilder { + private readonly mapW: number; + private readonly mapH: number; + private readonly tileState: Uint16Array; + private readonly palette: Float32Array; + private readonly knownSmallIDs = new Set(); + private readonly railroadCache: RailroadCache; + private readonly trailManager: TrailManager; + private readonly unitMap = new Map(); + private readonly trailIds: number[] = []; + + constructor( + private readonly view: WebGLGameView, + gameView: GameView, + ) { + this.mapW = gameView.width(); + this.mapH = gameView.height(); + this.tileState = new Uint16Array(this.mapW * this.mapH); + this.palette = new Float32Array(PALETTE_SIZE * 2 * 4); + this.railroadCache = new RailroadCache(this.mapW, this.mapH); + this.trailManager = new TrailManager(this.mapW, this.mapH); + } + + update(gameView: GameView, gu: GameUpdateViewData): void { + this.syncPlayers(gameView); + this.fillTileState(gameView); + this.fillUnitMap(gameView); + this.trailManager.update(this.unitMap, this.trailIds); + this.view.uploadTileAndTrailState( + this.tileState, + this.trailManager.getTrailState(), + ); + this.trailManager.clearDirtyRows(); + this.applyRailroads(gu); + this.view.updateStructures(this.unitMap); + this.view.updateUnits(this.unitMap, gameView.ticks()); + this.applyFxEvents(gameView, gu); + } + + private applyFxEvents(gameView: GameView, gu: GameUpdateViewData): void { + const deadUnits: DeadUnitFx[] = []; + for (const u of gu.updates[GameUpdateType.Unit] ?? []) { + if (u.isActive) continue; + deadUnits.push({ + unitType: u.unitType, + pos: u.pos, + reachedTarget: u.reachedTarget, + }); + } + if (deadUnits.length > 0) { + this.view.applyDeadUnits(deadUnits); + } + + const conquests: ConquestFx[] = []; + for (const c of gu.updates[GameUpdateType.ConquestEvent] ?? []) { + const conquered = gameView.player(c.conqueredId); + const loc = conquered.nameLocation(); + conquests.push({ + x: loc.x, + y: loc.y, + gold: Number(c.gold), + }); + } + if (conquests.length > 0) { + this.view.applyConquestEvents(conquests); + } + + const bonuses: BonusEvent[] = []; + for (const b of gu.updates[GameUpdateType.BonusEvent] ?? []) { + const player = gameView.player(b.player); + bonuses.push({ + playerID: b.player, + smallID: player.smallID(), + tile: b.tile, + gold: Number(b.gold), + troops: b.troops, + }); + } + if (bonuses.length > 0) { + this.view.applyBonusEvents(bonuses); + } + } + + private fillUnitMap(gameView: GameView): void { + this.unitMap.clear(); + this.trailIds.length = 0; + for (const u of gameView.units()) { + this.unitMap.set(u.id(), toUnitState(u)); + if (TRAIL_TYPES.has(u.type())) { + this.trailIds.push(u.id()); + } + } + } + + private applyRailroads(gu: GameUpdateViewData): void { + this.railroadCache.apply(gu); + if (this.railroadCache.railroadDirty) { + this.view.uploadRailroadState(this.railroadCache.railroadState); + this.railroadCache.clearDirty(); + } + if (this.railroadCache.revealedRailTiles.length > 0) { + this.view.applyRailroadDust(this.railroadCache.revealedRailTiles); + } + } + + private syncPlayers(gameView: GameView): void { + const newPlayers: PlayerStatic[] = []; + for (const p of gameView.players()) { + const smallID = p.smallID(); + if (this.knownSmallIDs.has(smallID)) continue; + this.knownSmallIDs.add(smallID); + + this.writePaletteEntry(smallID, p.territoryColor(), p.borderColor()); + + newPlayers.push({ + smallID, + id: p.id(), + name: p.name(), + displayName: p.displayName(), + clientID: p.clientID(), + playerType: gamePlayerTypeToEnum(p.type()), + team: p.team() ?? null, + isLobbyCreator: p.isLobbyCreator(), + color: p.territoryColor().toHex(), + }); + } + if (newPlayers.length > 0) { + this.view.addPlayers(newPlayers, this.palette); + } + } + + private writePaletteEntry( + smallID: number, + fill: Colord, + border: Colord, + ): void { + const fillRgba = fill.toRgb(); + const fillOff = smallID * 4; + this.palette[fillOff] = fillRgba.r / 255; + this.palette[fillOff + 1] = fillRgba.g / 255; + this.palette[fillOff + 2] = fillRgba.b / 255; + this.palette[fillOff + 3] = 150 / 255; + + const borderRgba = border.toRgb(); + const borderOff = PALETTE_SIZE * 4 + smallID * 4; + this.palette[borderOff] = borderRgba.r / 255; + this.palette[borderOff + 1] = borderRgba.g / 255; + this.palette[borderOff + 2] = borderRgba.b / 255; + this.palette[borderOff + 3] = 1.0; + } + + private fillTileState(gameView: GameView): void { + const w = this.mapW; + const h = this.mapH; + const buf = this.tileState; + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + const ref = gameView.ref(x, y); + let v = gameView.ownerID(ref) & 0x0fff; + if (gameView.hasFallout(ref)) v |= 1 << 13; + buf[y * w + x] = v; + } + } + } +} + +function toUnitState(u: import("../core/game/GameView").UnitView): UnitState { + return { + id: u.id(), + unitType: u.type(), + ownerID: u.owner().smallID(), + lastOwnerID: null, + pos: u.tile(), + lastPos: u.lastTile(), + isActive: u.isActive(), + reachedTarget: u.reachedTarget(), + retreating: false, + targetable: u.targetable(), + markedForDeletion: u.markedForDeletion(), + health: u.hasHealth() ? u.health() : null, + underConstruction: u.isUnderConstruction(), + targetUnitId: u.targetUnitId() ?? null, + targetTile: u.targetTile() ?? null, + troops: u.troops(), + missileTimerQueue: u.missileTimerQueue(), + level: u.level(), + hasTrainStation: u.hasTrainStation(), + trainType: trainTypeToNum(u.trainType()), + loaded: u.isLoaded() ?? null, + constructionStartTick: u.isUnderConstruction() ? u.createdAt() : null, + }; +} + +function trainTypeToNum(t: TrainType | undefined): number | null { + switch (t) { + case TrainType.Engine: + return RendererTrainType.Engine; + case TrainType.TailEngine: + return RendererTrainType.TailEngine; + case TrainType.Carriage: + return RendererTrainType.Carriage; + default: + return null; + } +} + +function gamePlayerTypeToEnum(t: PlayerType): PlayerTypeEnum { + switch (t) { + case PlayerType.Human: + return PlayerTypeEnum.Human; + case PlayerType.Bot: + return PlayerTypeEnum.Bot; + case PlayerType.Nation: + return PlayerTypeEnum.Nation; + default: + return PlayerTypeEnum.Bot; + } +} diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 4df65faccd..a25bff2787 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -13,11 +13,9 @@ import { BuildMenu } from "./layers/BuildMenu"; import { ChatDisplay } from "./layers/ChatDisplay"; import { ChatModal } from "./layers/ChatModal"; import { ControlPanel } from "./layers/ControlPanel"; -import { CoordinateGridLayer } from "./layers/CoordinateGridLayer"; import { DynamicUILayer } from "./layers/DynamicUILayer"; import { EmojiTable } from "./layers/EmojiTable"; import { EventsDisplay } from "./layers/EventsDisplay"; -import { FxLayer } from "./layers/FxLayer"; import { GameLeftSidebar } from "./layers/GameLeftSidebar"; import { GameRightSidebar } from "./layers/GameRightSidebar"; import { HeadsUpMessage } from "./layers/HeadsUpMessage"; @@ -28,23 +26,16 @@ import { Leaderboard } from "./layers/Leaderboard"; import { MainRadialMenu } from "./layers/MainRadialMenu"; import { MultiTabModal } from "./layers/MultiTabModal"; import { NameLayer } from "./layers/NameLayer"; -import { NukeTrajectoryPreviewLayer } from "./layers/NukeTrajectoryPreviewLayer"; import { PerformanceOverlay } from "./layers/PerformanceOverlay"; import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay"; import { PlayerPanel } from "./layers/PlayerPanel"; -import { RailroadLayer } from "./layers/RailroadLayer"; import { ReplayPanel } from "./layers/ReplayPanel"; -import { SAMRadiusLayer } from "./layers/SAMRadiusLayer"; import { SettingsModal } from "./layers/SettingsModal"; import { SpawnTimer } from "./layers/SpawnTimer"; import { StructureIconsLayer } from "./layers/StructureIconsLayer"; -import { StructureLayer } from "./layers/StructureLayer"; import { TeamStats } from "./layers/TeamStats"; -import { TerrainLayer } from "./layers/TerrainLayer"; -import { TerritoryLayer } from "./layers/TerritoryLayer"; import { UILayer } from "./layers/UILayer"; import { UnitDisplay } from "./layers/UnitDisplay"; -import { UnitLayer } from "./layers/UnitLayer"; import { WinModal } from "./layers/WinModal"; export function createRenderer( @@ -230,9 +221,6 @@ export function createRenderer( } headsUpMessage.game = game; - const structureLayer = new StructureLayer(game, eventBus, transformHandler); - const samRadiusLayer = new SAMRadiusLayer(game, eventBus, uiState); - const performanceOverlay = document.querySelector( "performance-overlay", ) as PerformanceOverlay; @@ -275,16 +263,7 @@ export function createRenderer( // Try to group layers by the return value of shouldTransform. // Not grouping the layers may cause excessive calls to context.save() and context.restore(). const layers: Layer[] = [ - new TerrainLayer(game, transformHandler), - new TerritoryLayer(game, eventBus, transformHandler), - new RailroadLayer(game, eventBus, transformHandler, uiState), - new CoordinateGridLayer(game, eventBus, transformHandler), - structureLayer, - samRadiusLayer, - new UnitLayer(game, eventBus, transformHandler), - new FxLayer(game, eventBus, transformHandler), new UILayer(game, eventBus, transformHandler), - new NukeTrajectoryPreviewLayer(game, eventBus, transformHandler, uiState), new StructureIconsLayer(game, eventBus, uiState, transformHandler), new DynamicUILayer(game, transformHandler, eventBus), new NameLayer(game, transformHandler, eventBus), @@ -338,6 +317,7 @@ export class GameRenderer { private layerTickState = new Map(); private renderFramesSinceLastTick: number = 0; private renderLayerDurationsSinceLastTick: Record = {}; + public onPreRender: (() => void) | null = null; constructor( private game: GameView, @@ -348,7 +328,7 @@ export class GameRenderer { private layers: Layer[], private performanceOverlay: PerformanceOverlay, ) { - const context = canvas.getContext("2d", { alpha: false }); + const context = canvas.getContext("2d", { alpha: true }); if (context === null) throw new Error("2d context not supported"); this.context = context; } @@ -399,13 +379,8 @@ export class GameRenderer { FrameProfiler.clear(); } const start = performance.now(); - // Set background - this.context.fillStyle = this.game - .config() - .theme() - .backgroundColor() - .toHex(); - this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.onPreRender?.(); + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); const handleTransformState = ( needsTransform: boolean, diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index 90966525c4..94e9535c9e 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -28,8 +28,8 @@ export const CAMERA_SMOOTHING = 0.03; export class TransformHandler { public scale: number = 1.8; private _boundingRect: DOMRect; - private offsetX: number = -350; - private offsetY: number = -200; + public offsetX: number = -350; + public offsetY: number = -200; private lastGoToCallTime: number | null = null; private target: Cell | null; diff --git a/src/client/graphics/layers/CoordinateGridLayer.ts b/src/client/graphics/layers/CoordinateGridLayer.ts deleted file mode 100644 index 885f76f82b..0000000000 --- a/src/client/graphics/layers/CoordinateGridLayer.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { EventBus } from "../../../core/EventBus"; -import { Cell } from "../../../core/game/Game"; -import { GameView } from "../../../core/game/GameView"; -import { - AlternateViewEvent, - ToggleCoordinateGridEvent, -} from "../../InputHandler"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; - -const BASE_CELL_COUNT = 10; -const MAX_COLUMNS = 50; -const MIN_ROWS = 2; -const LABEL_PADDING = 8; - -const toAlphaLabel = (index: number): string => { - let value = index; - let label = ""; - do { - label = String.fromCharCode(65 + (value % 26)) + label; - value = Math.floor(value / 26) - 1; - } while (value >= 0); - return label; -}; - -const computeGrid = (width: number, height: number) => { - // Initial square-ish estimate - let cellSize = Math.min(width, height) / BASE_CELL_COUNT; - let rows = Math.max(1, Math.round(height / cellSize)); - let cols = Math.max(1, Math.round(width / cellSize)); - - // Cap columns and adjust rows accordingly - if (cols > MAX_COLUMNS) { - const maxRowsForCols = Math.floor((MAX_COLUMNS * height) / width); - rows = Math.max(MIN_ROWS, Math.min(rows, maxRowsForCols)); - cols = MAX_COLUMNS; - } - - cellSize = Math.min(width / cols, height / rows); - const fullCols = Math.max(1, Math.floor(width / cellSize)); - const fullRows = Math.max(1, Math.floor(height / cellSize)); - - const remainderX = Math.max(0, width - fullCols * cellSize); - const remainderY = Math.max(0, height - fullRows * cellSize); - - const hasExtraCol = remainderX > 0.001; - const hasExtraRow = remainderY > 0.001; - - const totalCols = fullCols + (hasExtraCol ? 1 : 0); - const totalRows = fullRows + (hasExtraRow ? 1 : 0); - - const lastColWidth = hasExtraCol ? remainderX : cellSize; - const lastRowHeight = hasExtraRow ? remainderY : cellSize; - - return { - cellSize, - rows: totalRows, - cols: totalCols, - fullCols, - fullRows, - lastColWidth, - lastRowHeight, - hasExtraCol, - hasExtraRow, - gridWidth: width, - gridHeight: height, - }; -}; - -export class CoordinateGridLayer implements Layer { - private isVisible = false; - private alternateView = false; - private cachedGridCanvas: HTMLCanvasElement | null = null; - private cachedGridContext: CanvasRenderingContext2D | null = null; - private cachedGridKey = ""; - - constructor( - private game: GameView, - private eventBus: EventBus, - private transformHandler: TransformHandler, - ) {} - - init() { - this.eventBus.on(ToggleCoordinateGridEvent, (event) => { - this.isVisible = event.enabled; - }); - this.eventBus.on(AlternateViewEvent, (event) => { - this.alternateView = event.alternateView; - }); - } - - shouldTransform(): boolean { - return false; - } - - renderLayer(context: CanvasRenderingContext2D) { - if (!this.isVisible && !this.alternateView) return; - - const width = this.game.width(); - const height = this.game.height(); - if (width <= 0 || height <= 0) return; - const canvasWidth = context.canvas.width; - const canvasHeight = context.canvas.height; - - const cacheKey = this.buildCacheKey( - width, - height, - canvasWidth, - canvasHeight, - ); - const cacheContext = this.ensureCacheContext(canvasWidth, canvasHeight); - if (cacheContext === null || this.cachedGridCanvas === null) return; - - if (this.cachedGridKey !== cacheKey) { - cacheContext.clearRect(0, 0, canvasWidth, canvasHeight); - this.drawGrid(cacheContext, width, height); - this.cachedGridKey = cacheKey; - } - - context.drawImage(this.cachedGridCanvas, 0, 0); - } - - private ensureCacheContext( - canvasWidth: number, - canvasHeight: number, - ): CanvasRenderingContext2D | null { - this.cachedGridCanvas ??= document.createElement("canvas"); - - if ( - this.cachedGridCanvas.width !== canvasWidth || - this.cachedGridCanvas.height !== canvasHeight - ) { - this.cachedGridCanvas.width = canvasWidth; - this.cachedGridCanvas.height = canvasHeight; - this.cachedGridContext = null; - this.cachedGridKey = ""; - } - - this.cachedGridContext ??= this.cachedGridCanvas.getContext("2d"); - - return this.cachedGridContext; - } - - private buildCacheKey( - width: number, - height: number, - canvasWidth: number, - canvasHeight: number, - ): string { - const topLeft = this.transformHandler.worldToCanvasCoordinates( - new Cell(0, 0), - ); - const bottomRight = this.transformHandler.worldToCanvasCoordinates( - new Cell(width, height), - ); - const darkMode = this.game.config().userSettings()?.darkMode() ?? false; - return [ - width, - height, - canvasWidth, - canvasHeight, - this.transformHandler.scale.toFixed(4), - topLeft.x.toFixed(2), - topLeft.y.toFixed(2), - bottomRight.x.toFixed(2), - bottomRight.y.toFixed(2), - darkMode ? "1" : "0", - ].join("|"); - } - - private drawGrid( - context: CanvasRenderingContext2D, - width: number, - height: number, - ) { - const { - cellSize, - rows, - cols, - fullCols, - fullRows, - lastColWidth, - lastRowHeight, - hasExtraCol, - hasExtraRow, - gridWidth, - gridHeight, - } = computeGrid(width, height); - const cellWidth = cellSize; - const cellHeight = cellSize; - const canvasWidth = context.canvas.width; - const canvasHeight = context.canvas.height; - - const mapTopScreenRaw = this.transformHandler.worldToCanvasCoordinates( - new Cell(0, 0), - ).y; - const mapBottomScreenRaw = this.transformHandler.worldToCanvasCoordinates( - new Cell(0, height), - ).y; - const mapLeftScreenRaw = this.transformHandler.worldToCanvasCoordinates( - new Cell(0, 0), - ).x; - const mapRightScreenRaw = this.transformHandler.worldToCanvasCoordinates( - new Cell(width, 0), - ).x; - - const mapTopScreen = Math.min(mapTopScreenRaw, mapBottomScreenRaw); - const mapLeftScreen = Math.min(mapLeftScreenRaw, mapRightScreenRaw); - const mapTopWorld = 0; - const mapLeftWorld = 0; - - context.save(); - context.strokeStyle = "rgba(255, 255, 255, 0.35)"; - context.lineWidth = 1.25; - context.beginPath(); - - for (let col = 0; col <= fullCols; col++) { - const worldX = col * cellWidth + mapLeftWorld; - const screenX = this.transformHandler.worldToCanvasCoordinates( - new Cell(worldX, mapTopWorld), - ).x; - if (screenX < -1 || screenX > canvasWidth + 1) continue; - const screenBottom = this.transformHandler.worldToCanvasCoordinates( - new Cell(worldX, gridHeight), - ).y; - context.moveTo(screenX, mapTopScreen); - context.lineTo(screenX, screenBottom); - } - // Final vertical line at map right edge only if grid fits perfectly - if (!hasExtraCol) { - const mapRightLine = this.transformHandler.worldToCanvasCoordinates( - new Cell(gridWidth, mapTopWorld), - ).x; - context.moveTo(mapRightLine, mapTopScreen); - context.lineTo( - mapRightLine, - this.transformHandler.worldToCanvasCoordinates( - new Cell(gridWidth, gridHeight), - ).y, - ); - } - - for (let row = 0; row <= fullRows; row++) { - const worldY = row * cellHeight + mapTopWorld; - const screenY = this.transformHandler.worldToCanvasCoordinates( - new Cell(mapLeftWorld, worldY), - ).y; - if (screenY < -1 || screenY > canvasHeight + 1) continue; - const screenRight = this.transformHandler.worldToCanvasCoordinates( - new Cell(gridWidth, worldY), - ).x; - context.moveTo(mapLeftScreen, screenY); - context.lineTo(screenRight, screenY); - } - // Final horizontal line at map bottom edge only if grid fits perfectly - if (!hasExtraRow) { - const mapBottomLine = this.transformHandler.worldToCanvasCoordinates( - new Cell(mapLeftWorld, gridHeight), - ).y; - context.moveTo(mapLeftScreen, mapBottomLine); - context.lineTo( - this.transformHandler.worldToCanvasCoordinates( - new Cell(gridWidth, gridHeight), - ).x, - mapBottomLine, - ); - } - - context.stroke(); - - context.font = "12px monospace"; - - const isDarkMode = this.game.config().userSettings()?.darkMode() ?? false; - const drawLabel = (text: string, x: number, y: number) => { - context.textAlign = "left"; - context.textBaseline = "top"; - context.fillStyle = isDarkMode - ? "rgba(255, 255, 255, 0.9)" - : "rgba(20, 20, 20, 0.9)"; - context.fillText(text, x, y); - }; - - // Render per-cell labels (like A1, B1, etc.) at cell top-left - const fontSize = Math.min( - 16, - Math.max(9, 10 + (this.transformHandler.scale - 1) * 1.2), - ); - context.font = `${fontSize}px monospace`; - for (let row = 0; row < rows; row++) { - const rowLabel = toAlphaLabel(row); - const startY = row * cellHeight; - const rowHeight = row < fullRows ? cellHeight : lastRowHeight; - const centerY = startY + rowHeight / 2; - const screenY = this.transformHandler.worldToCanvasCoordinates( - new Cell(0, centerY), - ).y; - if (screenY < -LABEL_PADDING || screenY > canvasHeight + LABEL_PADDING) - continue; - - for (let col = 0; col < cols; col++) { - const startX = col * cellWidth; - const colWidth = col < fullCols ? cellWidth : lastColWidth; - const centerX = startX + colWidth / 2; - const screenX = this.transformHandler.worldToCanvasCoordinates( - new Cell(centerX, centerY), - ).x; - if (screenX < -LABEL_PADDING || screenX > canvasWidth + LABEL_PADDING) - continue; - - // Position at cell top-left in screen space - const cellTopLeft = this.transformHandler.worldToCanvasCoordinates( - new Cell(startX, startY), - ); - drawLabel( - `${rowLabel}${col + 1}`, - cellTopLeft.x + LABEL_PADDING, - cellTopLeft.y + LABEL_PADDING, - ); - } - } - - context.restore(); - } -} diff --git a/src/client/graphics/layers/FxLayer.ts b/src/client/graphics/layers/FxLayer.ts deleted file mode 100644 index 22ce78ff5b..0000000000 --- a/src/client/graphics/layers/FxLayer.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { Theme } from "src/core/configuration/Theme"; -import { EventBus } from "../../../core/EventBus"; -import { UnitType } from "../../../core/game/Game"; -import { TileRef } from "../../../core/game/GameMap"; -import { ConquestUpdate, GameUpdateType } from "../../../core/game/GameUpdates"; -import { GameView, UnitView } from "../../../core/game/GameView"; -import { PlaySoundEffectEvent } from "../../sound/Sounds"; -import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader"; -import { conquestFxFactory } from "../fx/ConquestFx"; -import { Fx, FxType } from "../fx/Fx"; -import { nukeFxFactory, ShockwaveFx } from "../fx/NukeFx"; -import { SpriteFx } from "../fx/SpriteFx"; -import { UnitExplosionFx } from "../fx/UnitExplosionFx"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; -import { RailTileChangedEvent } from "./RailroadLayer"; -export class FxLayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D; - - private lastRefreshMs: number = 0; - private refreshRate: number = 10; - private theme: Theme; - private animatedSpriteLoader: AnimatedSpriteLoader = - new AnimatedSpriteLoader(); - - private allFx: Fx[] = []; - private hasBufferedFrame = false; - - constructor( - private game: GameView, - private eventBus: EventBus, - private transformHandler: TransformHandler, - ) { - this.theme = this.game.config().theme(); - } - - shouldTransform(): boolean { - return true; - } - - private fxEnabled(): boolean { - return this.game.config().userSettings()?.fxLayer() ?? true; - } - - tick() { - this.game - .updatesSinceLastTick() - ?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id)) - ?.forEach((unitView) => { - if (unitView === undefined) return; - this.onUnitEvent(unitView); - }); - this.game - .updatesSinceLastTick() - ?.[GameUpdateType.ConquestEvent]?.forEach((update) => { - if (update === undefined) return; - this.onConquestEvent(update); - }); - } - - onUnitEvent(unit: UnitView) { - // Detect unit creation (launches, warship built) - if (unit.isActive() && unit.createdAt() === this.game.ticks()) { - this.onUnitCreated(unit); - } - - switch (unit.type()) { - case UnitType.AtomBomb: { - this.onNukeEvent(unit, 70); - break; - } - case UnitType.MIRVWarhead: - this.onNukeEvent(unit, 70); - break; - case UnitType.HydrogenBomb: { - this.onNukeEvent(unit, 160); - break; - } - case UnitType.Warship: - this.onWarshipEvent(unit); - break; - case UnitType.Shell: - this.onShellEvent(unit); - break; - case UnitType.Train: - this.onTrainEvent(unit); - break; - case UnitType.DefensePost: - case UnitType.City: - case UnitType.Port: - case UnitType.MissileSilo: - case UnitType.SAMLauncher: - case UnitType.Factory: - this.onStructureEvent(unit); - break; - } - } - - onUnitCreated(unit: UnitView) { - switch (unit.type()) { - case UnitType.AtomBomb: - this.eventBus.emit(new PlaySoundEffectEvent("atom-launch")); - break; - case UnitType.HydrogenBomb: - this.eventBus.emit(new PlaySoundEffectEvent("hydrogen-launch")); - break; - case UnitType.MIRV: - this.eventBus.emit(new PlaySoundEffectEvent("mirv-launch")); - break; - case UnitType.Warship: - if (unit.owner() === this.game.myPlayer()) { - this.eventBus.emit(new PlaySoundEffectEvent("build-warship")); - } - break; - case UnitType.City: - if (unit.owner() === this.game.myPlayer()) { - this.eventBus.emit(new PlaySoundEffectEvent("build-city")); - } - break; - case UnitType.Port: - if (unit.owner() === this.game.myPlayer()) { - this.eventBus.emit(new PlaySoundEffectEvent("build-port")); - } - break; - case UnitType.DefensePost: - if (unit.owner() === this.game.myPlayer()) { - this.eventBus.emit(new PlaySoundEffectEvent("build-defense-post")); - } - break; - case UnitType.SAMLauncher: - if (unit.owner() === this.game.myPlayer()) { - this.eventBus.emit(new PlaySoundEffectEvent("sam-built")); - } - break; - } - } - - onShellEvent(unit: UnitView) { - if (!unit.isActive()) { - if (unit.reachedTarget() && this.fxEnabled()) { - const x = this.game.x(unit.lastTile()); - const y = this.game.y(unit.lastTile()); - const explosion = new SpriteFx( - this.animatedSpriteLoader, - x, - y, - FxType.MiniExplosion, - ); - this.allFx.push(explosion); - } - } - } - - onTrainEvent(unit: UnitView) { - if (!unit.isActive()) { - if (!unit.reachedTarget() && this.fxEnabled()) { - const x = this.game.x(unit.lastTile()); - const y = this.game.y(unit.lastTile()); - const explosion = new SpriteFx( - this.animatedSpriteLoader, - x, - y, - FxType.MiniExplosion, - ); - this.allFx.push(explosion); - } - } - } - - onRailroadEvent(tile: TileRef) { - if (!this.fxEnabled()) return; - // No need for pseudorandom, this is fx - const chanceFx = Math.floor(Math.random() * 3); - if (chanceFx === 0) { - const x = this.game.x(tile); - const y = this.game.y(tile); - const animation = new SpriteFx( - this.animatedSpriteLoader, - x, - y, - FxType.Dust, - ); - this.allFx.push(animation); - } - } - - onConquestEvent(conquest: ConquestUpdate) { - // Only display fx for the current player - const conqueror = this.game.player(conquest.conquerorId); - if (conqueror !== this.game.myPlayer()) { - return; - } - - this.eventBus.emit(new PlaySoundEffectEvent("ka-ching")); - - if (this.fxEnabled()) { - this.allFx.push( - conquestFxFactory(this.animatedSpriteLoader, conquest, this.game), - ); - } - } - - onWarshipEvent(unit: UnitView) { - if (!unit.isActive() && this.fxEnabled()) { - const x = this.game.x(unit.lastTile()); - const y = this.game.y(unit.lastTile()); - const shipExplosion = new UnitExplosionFx( - this.animatedSpriteLoader, - x, - y, - this.game, - ); - this.allFx.push(shipExplosion); - const sinkingShip = new SpriteFx( - this.animatedSpriteLoader, - x, - y, - FxType.SinkingShip, - undefined, - unit.owner(), - this.theme, - ); - this.allFx.push(sinkingShip); - } - } - - onStructureEvent(unit: UnitView) { - if (!unit.isActive() && this.fxEnabled()) { - const x = this.game.x(unit.lastTile()); - const y = this.game.y(unit.lastTile()); - const explosion = new SpriteFx( - this.animatedSpriteLoader, - x, - y, - FxType.BuildingExplosion, - ); - this.allFx.push(explosion); - } - } - - onNukeEvent(unit: UnitView, radius: number) { - if (!unit.isActive()) { - if (!unit.reachedTarget()) { - this.handleSAMInterception(unit); - } else { - // Kaboom - this.handleNukeExplosion(unit, radius); - } - } - } - - handleNukeExplosion(unit: UnitView, radius: number) { - if (this.fxEnabled()) { - const x = this.game.x(unit.lastTile()); - const y = this.game.y(unit.lastTile()); - const nukeFx = nukeFxFactory( - this.animatedSpriteLoader, - x, - y, - radius, - this.game, - ); - this.allFx = this.allFx.concat(nukeFx); - } - const sound = - unit.type() === UnitType.HydrogenBomb ? "hydrogen-hit" : "atom-hit"; - this.eventBus.emit(new PlaySoundEffectEvent(sound)); - } - - handleSAMInterception(unit: UnitView) { - if (this.fxEnabled()) { - const x = this.game.x(unit.lastTile()); - const y = this.game.y(unit.lastTile()); - const explosion = new SpriteFx( - this.animatedSpriteLoader, - x, - y, - FxType.SAMExplosion, - ); - this.allFx.push(explosion); - const shockwave = new ShockwaveFx(x, y, 800, 40); - this.allFx.push(shockwave); - } - } - - async init() { - this.redraw(); - - this.eventBus.on(RailTileChangedEvent, (e) => { - this.onRailroadEvent(e.tile); - }); - try { - this.animatedSpriteLoader.loadAllAnimatedSpriteImages(); - console.log("FX sprites loaded successfully"); - } catch (err) { - console.error("Failed to load FX sprites:", err); - } - } - - redraw(): void { - this.canvas = document.createElement("canvas"); - const context = this.canvas.getContext("2d"); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - this.context.imageSmoothingEnabled = false; - this.canvas.width = this.game.width(); - this.canvas.height = this.game.height(); - } - - renderLayer(context: CanvasRenderingContext2D) { - const nowMs = performance.now(); - - const hasFx = this.allFx.length > 0; - if (!this.game.config().userSettings()?.fxLayer() || !hasFx) { - if (this.hasBufferedFrame) { - // Clear stale pixels once when fx ends/disabled so re-enabling doesn't - // flash old frames. - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.hasBufferedFrame = false; - } - this.lastRefreshMs = nowMs; - return; - } - - const needsRefresh = - !this.hasBufferedFrame || nowMs > this.lastRefreshMs + this.refreshRate; - if (needsRefresh) { - const delta = this.hasBufferedFrame ? nowMs - this.lastRefreshMs : 0; - this.renderAllFx(delta); - this.lastRefreshMs = nowMs; - this.hasBufferedFrame = true; - } - - this.drawVisibleFx(context); - } - - private drawVisibleFx(context: CanvasRenderingContext2D) { - const mapW = this.game.width(); - const mapH = this.game.height(); - - const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect(); - const pad = 2; - - const left = Math.max(0, Math.floor(topLeft.x - pad)); - const top = Math.max(0, Math.floor(topLeft.y - pad)); - const right = Math.min(mapW, Math.ceil(bottomRight.x + pad)); - const bottom = Math.min(mapH, Math.ceil(bottomRight.y + pad)); - - const width = Math.max(0, right - left); - const height = Math.max(0, bottom - top); - if (width === 0 || height === 0) return; - - context.drawImage( - this.canvas, - left, - top, - width, - height, - -mapW / 2 + left, - -mapH / 2 + top, - width, - height, - ); - } - - private renderAllFx(delta: number) { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.renderContextFx(delta); - } - - renderContextFx(duration: number) { - for (let i = this.allFx.length - 1; i >= 0; i--) { - if (!this.allFx[i].renderTick(duration, this.context)) { - this.allFx.splice(i, 1); - } - } - } -} diff --git a/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts b/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts deleted file mode 100644 index 447f92d873..0000000000 --- a/src/client/graphics/layers/NukeTrajectoryPreviewLayer.ts +++ /dev/null @@ -1,428 +0,0 @@ -import { EventBus } from "../../../core/EventBus"; -import { listNukeBreakAlliance } from "../../../core/execution/Util"; -import { UnitType } from "../../../core/game/Game"; -import { TileRef } from "../../../core/game/GameMap"; -import { GameView } from "../../../core/game/GameView"; -import { UniversalPathFinding } from "../../../core/pathfinding/PathFinder"; -import { - GhostStructureChangedEvent, - MouseMoveEvent, - SwapRocketDirectionEvent, -} from "../../InputHandler"; -import { TransformHandler } from "../TransformHandler"; -import { UIState } from "../UIState"; -import { Layer } from "./Layer"; - -/** - * Layer responsible for rendering the nuke trajectory preview line - * when a nuke type (AtomBomb or HydrogenBomb) is selected and the user hovers over potential targets. - */ -export class NukeTrajectoryPreviewLayer implements Layer { - // Trajectory preview state - private mousePos = { x: 0, y: 0 }; - private trajectoryPoints: TileRef[] = []; - private untargetableSegmentBounds: [number, number] = [-1, -1]; - private targetedIndex = -1; - private lastTrajectoryUpdate: number = 0; - private lastTargetTile: TileRef | null = null; - private currentGhostStructure: UnitType | null = null; - // Cache spawn tile to avoid expensive player.buildables() calls - private cachedSpawnTile: TileRef | null = null; - - constructor( - private game: GameView, - private eventBus: EventBus, - private transformHandler: TransformHandler, - private uiState: UIState, - ) {} - - shouldTransform(): boolean { - return true; - } - - init() { - this.eventBus.on(MouseMoveEvent, (e) => { - this.mousePos.x = e.x; - this.mousePos.y = e.y; - }); - this.eventBus.on(GhostStructureChangedEvent, (e) => { - this.currentGhostStructure = e.ghostStructure; - // Clear trajectory if ghost structure changed - if ( - e.ghostStructure !== UnitType.AtomBomb && - e.ghostStructure !== UnitType.HydrogenBomb - ) { - this.trajectoryPoints = []; - this.lastTargetTile = null; - this.cachedSpawnTile = null; - } - }); - this.eventBus.on(SwapRocketDirectionEvent, (event) => { - this.uiState.rocketDirectionUp = event.rocketDirectionUp; - // Force trajectory recalculation - this.lastTargetTile = null; - }); - } - - tick() { - this.updateTrajectoryPreview(); - } - - renderLayer(context: CanvasRenderingContext2D) { - // Update trajectory path each frame for smooth responsiveness - this.updateTrajectoryPath(); - this.drawTrajectoryPreview(context); - } - - /** - * Update trajectory preview - called from tick() to cache spawn tile via expensive player.buildables() call - * This only runs when target tile changes, minimizing worker thread communication - */ - private updateTrajectoryPreview() { - const ghostStructure = this.currentGhostStructure; - const isNukeType = - ghostStructure === UnitType.AtomBomb || - ghostStructure === UnitType.HydrogenBomb; - - // Clear trajectory if not a nuke type - if (!isNukeType) { - this.cachedSpawnTile = null; - return; - } - - // Throttle updates (similar to StructureIconsLayer.renderGhost) - const now = performance.now(); - if (now - this.lastTrajectoryUpdate < 50) { - return; - } - this.lastTrajectoryUpdate = now; - - const player = this.game.myPlayer(); - if (!player) { - this.trajectoryPoints = []; - this.lastTargetTile = null; - this.cachedSpawnTile = null; - return; - } - - // Convert mouse position to world coordinates - const worldCoords = this.transformHandler.screenToWorldCoordinates( - this.mousePos.x, - this.mousePos.y, - ); - - if (!this.game.isValidCoord(worldCoords.x, worldCoords.y)) { - this.trajectoryPoints = []; - this.lastTargetTile = null; - this.cachedSpawnTile = null; - return; - } - - const targetTile = this.game.ref(worldCoords.x, worldCoords.y); - - // Only recalculate if target tile changed - if (this.lastTargetTile === targetTile) { - return; - } - - this.lastTargetTile = targetTile; - - // Get buildable units to find spawn tile (expensive call - only on tick when tile changes) - player - .buildables(targetTile, [ghostStructure]) - .then((buildables) => { - // Ignore stale results if target changed - if (this.lastTargetTile !== targetTile) { - return; - } - - const buildableUnit = buildables.find( - (bu) => bu.type === ghostStructure, - ); - - if (!buildableUnit || buildableUnit.canBuild === false) { - this.cachedSpawnTile = null; - return; - } - - const spawnTile = buildableUnit.canBuild; - if (!spawnTile) { - this.cachedSpawnTile = null; - return; - } - - // Cache the spawn tile for use in updateTrajectoryPath() - this.cachedSpawnTile = spawnTile; - }) - .catch(() => { - // Handle errors silently - this.cachedSpawnTile = null; - }); - } - - /** - * Update trajectory path - called from renderLayer() each frame for smooth visual feedback - * Uses cached spawn tile to avoid expensive player.buildables() calls - */ - private updateTrajectoryPath() { - const ghostStructure = this.currentGhostStructure; - const isNukeType = - ghostStructure === UnitType.AtomBomb || - ghostStructure === UnitType.HydrogenBomb; - - // Clear trajectory if not a nuke type or no cached spawn tile - if (!isNukeType || !this.cachedSpawnTile) { - this.trajectoryPoints = []; - return; - } - - const player = this.game.myPlayer(); - if (!player) { - this.trajectoryPoints = []; - return; - } - - // Convert mouse position to world coordinates - const worldCoords = this.transformHandler.screenToWorldCoordinates( - this.mousePos.x, - this.mousePos.y, - ); - - if (!this.game.isValidCoord(worldCoords.x, worldCoords.y)) { - this.trajectoryPoints = []; - return; - } - - const targetTile = this.game.ref(worldCoords.x, worldCoords.y); - - // Calculate trajectory using ParabolaUniversalPathFinder with cached spawn tile - const speed = this.game.config().defaultNukeSpeed(); - const pathFinder = UniversalPathFinding.Parabola(this.game, { - increment: speed, - distanceBasedHeight: true, // AtomBomb/HydrogenBomb use distance-based height - directionUp: this.uiState.rocketDirectionUp, - }); - - this.trajectoryPoints = - pathFinder.findPath(this.cachedSpawnTile, targetTile) ?? []; - - // NOTE: This is a lot to do in the rendering method, naive - // But trajectory is already calculated here and needed for prediction. - // From testing, does not seem to have much effect, so I keep it this way. - - // Calculate points when bomb targetability switches - const targetRangeSquared = - this.game.config().defaultNukeTargetableRange() ** 2; - - // Find two switch points where bomb transitions: - // [0]: leaves spawn range, enters untargetable zone - // [1]: enters target range, becomes targetable again - this.untargetableSegmentBounds = [-1, -1]; - for (let i = 0; i < this.trajectoryPoints.length; i++) { - const tile = this.trajectoryPoints[i]; - if (this.untargetableSegmentBounds[0] === -1) { - if ( - this.game.euclideanDistSquared(tile, this.cachedSpawnTile) > - targetRangeSquared - ) { - if ( - this.game.euclideanDistSquared(tile, targetTile) < - targetRangeSquared - ) { - // overlapping spawn & target range - break; - } else { - this.untargetableSegmentBounds[0] = i; - } - } - } else if ( - this.game.euclideanDistSquared(tile, targetTile) < targetRangeSquared - ) { - this.untargetableSegmentBounds[1] = i; - break; - } - } - const playersToBreakAllianceWith = listNukeBreakAlliance({ - game: this.game, - targetTile, - magnitude: this.game.config().nukeMagnitudes(ghostStructure), - threshold: this.game.config().nukeAllianceBreakThreshold(), - }); - // Find the point where SAM can intercept - this.targetedIndex = this.trajectoryPoints.length; - // Check trajectory - for (let i = 0; i < this.trajectoryPoints.length; i++) { - const tile = this.trajectoryPoints[i]; - for (const sam of this.game.nearbyUnits( - tile, - this.game.config().maxSamRange(), - UnitType.SAMLauncher, - )) { - if ( - sam.unit.owner().isMe() || - (this.game.myPlayer()?.isFriendly(sam.unit.owner()) && - !playersToBreakAllianceWith.has(sam.unit.owner().smallID())) - ) { - continue; - } - if ( - sam.distSquared <= - this.game.config().samRange(sam.unit.level()) ** 2 - ) { - this.targetedIndex = i; - break; - } - } - if (this.targetedIndex !== this.trajectoryPoints.length) break; - // Jump over untargetable segment - if (i === this.untargetableSegmentBounds[0]) - i = this.untargetableSegmentBounds[1] - 1; - } - } - - /** - * Draw trajectory preview line on the canvas - */ - private drawTrajectoryPreview(context: CanvasRenderingContext2D) { - const ghostStructure = this.currentGhostStructure; - const isNukeType = - ghostStructure === UnitType.AtomBomb || - ghostStructure === UnitType.HydrogenBomb; - - if (!isNukeType || this.trajectoryPoints.length === 0) { - return; - } - - const player = this.game.myPlayer(); - if (!player) { - return; - } - - // Set of line colors, targeted is after SAM intercept is detected. - const untargetedOutlineColor = "rgba(140, 140, 140, 1)"; - const targetedOutlineColor = "rgba(150, 90, 90, 1)"; - const symbolOutlineColor = "rgba(0, 0, 0, 1)"; - const targetedLocationColor = "rgba(255, 0, 0, 1)"; - const untargetableAndUntargetedLineColor = "rgba(255, 255, 255, 1)"; - const targetableAndUntargetedLineColor = "rgba(255, 255, 255, 1)"; - const untargetableAndTargetedLineColor = "rgba(255, 80, 80, 1)"; - const targetableAndTargetedLineColor = "rgba(255, 80, 80, 1)"; - - // Set of line widths - const outlineExtraWidth = 1.5; // adds onto below - const lineWidth = 1.25; - const XLineWidth = 2; - const XSize = 6; - - // Set of line dashes - // Outline dashes calculated automatically - const untargetableAndUntargetedLineDash = [2, 6]; - const targetableAndUntargetedLineDash = [8, 4]; - const untargetableAndTargetedLineDash = [2, 6]; - const targetableAndTargetedLineDash = [8, 4]; - - const outlineDash = (dash: number[], extra: number) => { - return [dash[0] + extra, Math.max(dash[1] - extra, 0)]; - }; - - // Tracks the change of color and dash length throughout - let currentOutlineColor = untargetedOutlineColor; - let currentLineColor = targetableAndUntargetedLineColor; - let currentLineDash = targetableAndUntargetedLineDash; - let currentLineWidth = lineWidth; - - // Take in set of "current" parameters and draw both outline and line. - const outlineAndStroke = () => { - context.lineWidth = currentLineWidth + outlineExtraWidth; - context.setLineDash(outlineDash(currentLineDash, outlineExtraWidth)); - context.lineDashOffset = outlineExtraWidth / 2; - context.strokeStyle = currentOutlineColor; - context.stroke(); - context.lineWidth = currentLineWidth; - context.setLineDash(currentLineDash); - context.lineDashOffset = 0; - context.strokeStyle = currentLineColor; - context.stroke(); - }; - const drawUntargetableCircle = (x: number, y: number) => { - context.beginPath(); - context.arc(x, y, 4, 0, 2 * Math.PI, false); - currentOutlineColor = untargetedOutlineColor; - currentLineColor = targetableAndUntargetedLineColor; - currentLineDash = [1, 0]; - outlineAndStroke(); - }; - const drawTargetedX = (x: number, y: number) => { - context.beginPath(); - context.moveTo(x - XSize, y - XSize); - context.lineTo(x + XSize, y + XSize); - context.moveTo(x - XSize, y + XSize); - context.lineTo(x + XSize, y - XSize); - currentOutlineColor = symbolOutlineColor; - currentLineColor = targetedLocationColor; - currentLineDash = [1, 0]; - currentLineWidth = XLineWidth; - outlineAndStroke(); - }; - - // Calculate offset to center coordinates (same as canvas drawing) - const offsetX = -this.game.width() / 2; - const offsetY = -this.game.height() / 2; - - context.save(); - context.beginPath(); - - // Draw line connecting trajectory points - for (let i = 0; i < this.trajectoryPoints.length; i++) { - const tile = this.trajectoryPoints[i]; - const x = this.game.x(tile) + offsetX; - const y = this.game.y(tile) + offsetY; - - if (i === 0) { - context.moveTo(x, y); - } else { - context.lineTo(x, y); - } - if (i === this.untargetableSegmentBounds[0]) { - outlineAndStroke(); - drawUntargetableCircle(x, y); - context.beginPath(); - if (i >= this.targetedIndex) { - currentOutlineColor = targetedOutlineColor; - currentLineColor = untargetableAndTargetedLineColor; - currentLineDash = untargetableAndTargetedLineDash; - } else { - currentOutlineColor = untargetedOutlineColor; - currentLineColor = untargetableAndUntargetedLineColor; - currentLineDash = untargetableAndUntargetedLineDash; - } - } else if (i === this.untargetableSegmentBounds[1]) { - outlineAndStroke(); - drawUntargetableCircle(x, y); - context.beginPath(); - if (i >= this.targetedIndex) { - currentOutlineColor = targetedOutlineColor; - currentLineColor = targetableAndTargetedLineColor; - currentLineDash = targetableAndTargetedLineDash; - } else { - currentOutlineColor = untargetedOutlineColor; - currentLineColor = targetableAndUntargetedLineColor; - currentLineDash = targetableAndUntargetedLineDash; - } - } - if (i === this.targetedIndex) { - outlineAndStroke(); - drawTargetedX(x, y); - context.beginPath(); - // Always in the targetable zone by definition. - currentOutlineColor = targetedOutlineColor; - currentLineColor = targetableAndTargetedLineColor; - currentLineDash = targetableAndTargetedLineDash; - currentLineWidth = lineWidth; - } - } - - outlineAndStroke(); - context.restore(); - } -} diff --git a/src/client/graphics/layers/RailroadLayer.ts b/src/client/graphics/layers/RailroadLayer.ts deleted file mode 100644 index 367c77a27d..0000000000 --- a/src/client/graphics/layers/RailroadLayer.ts +++ /dev/null @@ -1,501 +0,0 @@ -import { colord } from "colord"; -import { EventBus, GameEvent } from "../../../core/EventBus"; -import { PlayerID, UnitType } from "../../../core/game/Game"; -import { TileRef } from "../../../core/game/GameMap"; -import { - GameUpdateType, - RailroadConstructionUpdate, - RailroadDestructionUpdate, - RailroadSnapUpdate, -} from "../../../core/game/GameUpdates"; -import { GameView } from "../../../core/game/GameView"; -import { AlternateViewEvent } from "../../InputHandler"; -import { TransformHandler } from "../TransformHandler"; -import { UIState } from "../UIState"; -import { Layer } from "./Layer"; -import { getBridgeRects, getRailroadRects } from "./RailroadSprites"; -import { - computeRailTiles, - RailroadView, - RailTile, - RailType, -} from "./RailroadView"; - -type RailRef = { - tile: RailTile; - numOccurence: number; - lastOwnerId: PlayerID | null; -}; -const SNAPPABLE_STRUCTURES: UnitType[] = [ - UnitType.Port, - UnitType.City, - UnitType.Factory, -]; -export class RailTileChangedEvent implements GameEvent { - constructor(public tile: TileRef) {} -} - -export class RailroadLayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D; - private alternativeView = false; - // Save the number of railroads per tiles. Delete when it reaches 0 - private existingRailroads = new Map(); - private railroads = new Map(); - // Railroads under construction - private pendingRailroads = new Set(); - private nextRailIndexToCheck = 0; - private railTileList: TileRef[] = []; - private railTileIndex = new Map(); - private lastRailColorUpdate = 0; - private readonly railColorIntervalMs = 50; - - constructor( - private game: GameView, - private eventBus: EventBus, - private transformHandler: TransformHandler, - private uiState: UIState, - ) {} - - shouldTransform(): boolean { - return true; - } - - tick() { - this.updatePendingRailroads(); - const updates = this.game.updatesSinceLastTick(); - if (!updates) return; - // The event has to be handled in this specific order: construction / snap / destruction - // Otherwise some ID may not be available yet/anymore - updates[GameUpdateType.RailroadConstructionEvent]?.forEach((update) => { - if (update === undefined) return; - this.onRailroadConstruction(update); - }); - updates[GameUpdateType.RailroadSnapEvent]?.forEach((update) => { - if (update === undefined) return; - this.onRailroadSnapEvent(update); - }); - updates[GameUpdateType.RailroadDestructionEvent]?.forEach((update) => { - if (update === undefined) return; - this.onRailroadDestruction(update); - }); - } - - updatePendingRailroads() { - for (const id of this.pendingRailroads) { - const pending = this.railroads.get(id); - if (pending === undefined) { - // Rail deleted or snapped before the end of the animation - this.pendingRailroads.delete(id); - continue; - } - const newTiles = pending.tick(); - if (newTiles.length === 0) { - // Animation complete - this.pendingRailroads.delete(id); - continue; - } - - for (const railTile of newTiles) { - this.paintRailTile(railTile); - this.eventBus.emit(new RailTileChangedEvent(railTile.tile)); - } - } - } - - updateRailColors() { - if (this.railTileList.length === 0) { - return; - } - // Throttle color checks so we do not re-evaluate on every frame - const now = performance.now(); - if (now - this.lastRailColorUpdate < this.railColorIntervalMs) { - return; - } - this.lastRailColorUpdate = now; - - // Spread work over multiple frames to avoid large bursts when many rails exist - const maxTilesPerFrame = Math.max( - 1, - Math.ceil(this.railTileList.length / 120), - ); - let checked = 0; - - while (checked < maxTilesPerFrame && this.railTileList.length > 0) { - const tile = this.railTileList[this.nextRailIndexToCheck]; - const railRef = this.existingRailroads.get(tile); - if (railRef) { - const currentOwner = this.game.owner(tile)?.id() ?? null; - if (railRef.lastOwnerId !== currentOwner) { - // Repaint only when the owner changed to keep colors in sync - railRef.lastOwnerId = currentOwner; - this.paintRail(railRef.tile); - } - } - - this.nextRailIndexToCheck = - (this.nextRailIndexToCheck + 1) % this.railTileList.length; - checked++; - } - } - - init() { - this.eventBus.on(AlternateViewEvent, (e) => { - this.alternativeView = e.alternateView; - for (const { tile } of this.existingRailroads.values()) { - this.paintRail(tile); - } - }); - this.redraw(); - } - - redraw() { - this.canvas = document.createElement("canvas"); - const context = this.canvas.getContext("2d", { alpha: true }); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - - // Firefox's GPU limit is 8192, only known browser issue - const maxTextureSize = 8192; - const scaleX = maxTextureSize / this.game.width(); - const scaleY = maxTextureSize / this.game.height(); - const targetScale = Math.min(2, scaleX, scaleY); - - this.canvas.width = Math.max( - 1, - Math.floor(this.game.width() * targetScale), - ); - this.canvas.height = Math.max( - 1, - Math.floor(this.game.height() * targetScale), - ); - - // Enable smooth scaling - this.context.imageSmoothingEnabled = true; - this.context.imageSmoothingQuality = "high"; - - // Scale context so existing *2 rendering math continues to work automatically - this.context.scale( - this.canvas.width / (this.game.width() * 2), - this.canvas.height / (this.game.height() * 2), - ); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const [_, rail] of this.existingRailroads) { - this.paintRail(rail.tile); - } - } - - private highlightOverlappingRailroads(context: CanvasRenderingContext2D) { - if ( - this.uiState.ghostStructure === null || - !SNAPPABLE_STRUCTURES.includes(this.uiState.ghostStructure) - ) - return; - if ( - this.uiState.overlappingRailroads === undefined || - this.uiState.overlappingRailroads.length === 0 - ) - return; - const offsetX = -this.game.width() / 2; - const offsetY = -this.game.height() / 2; - context.fillStyle = "rgba(0, 255, 0, 0.4)"; - for (const id of this.uiState.overlappingRailroads) { - const rail = this.railroads.get(id); - if (rail) { - for (const railTile of rail.drawnTiles()) { - const x = this.game.x(railTile.tile); - const y = this.game.y(railTile.tile); - context.fillRect(x + offsetX - 1, y + offsetY - 1, 2.5, 2.5); - } - } - } - } - - renderLayer(context: CanvasRenderingContext2D) { - const scale = this.transformHandler.scale; - if (scale <= 1) { - return; - } - this.updateRailColors(); - const rawAlpha = (scale - 1) / (2 - 1); // maps 1->0, 2->1 - const alpha = Math.max(0, Math.min(1, rawAlpha)); - - const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect(); - const padding = 2; // small margin so edges do not pop - const visLeft = Math.max(0, topLeft.x - padding); - const visTop = Math.max(0, topLeft.y - padding); - const visRight = Math.min(this.game.width(), bottomRight.x + padding); - const visBottom = Math.min(this.game.height(), bottomRight.y + padding); - const visWidth = Math.max(0, visRight - visLeft); - const visHeight = Math.max(0, visBottom - visTop); - if (visWidth === 0 || visHeight === 0) { - return; - } - - const actualScaleX = this.canvas.width / this.game.width(); - const actualScaleY = this.canvas.height / this.game.height(); - - const srcX = visLeft * actualScaleX; - const srcY = visTop * actualScaleY; - const srcW = visWidth * actualScaleX; - const srcH = visHeight * actualScaleY; - - const dstX = -this.game.width() / 2 + visLeft; - const dstY = -this.game.height() / 2 + visTop; - - context.save(); - context.globalAlpha = alpha; - - this.renderGhostRailroads(context); - - if (this.existingRailroads.size > 0) { - this.highlightOverlappingRailroads(context); - - context.drawImage( - this.canvas, - srcX, - srcY, - srcW, - srcH, - dstX, - dstY, - visWidth, - visHeight, - ); - } - - context.restore(); - } - - private renderGhostRailroads(context: CanvasRenderingContext2D) { - if ( - this.uiState.ghostStructure !== UnitType.City && - this.uiState.ghostStructure !== UnitType.Port - ) - return; - if (this.uiState.ghostRailPaths.length === 0) return; - - const offsetX = -this.game.width() / 2; - const offsetY = -this.game.height() / 2; - context.fillStyle = "rgba(0, 0, 0, 0.4)"; - - for (const path of this.uiState.ghostRailPaths) { - const railTiles = computeRailTiles(this.game, path); - for (const railTile of railTiles) { - const x = this.game.x(railTile.tile); - const y = this.game.y(railTile.tile); - - if (this.game.isWater(railTile.tile)) { - context.save(); - context.fillStyle = "rgba(197, 69, 72, 0.4)"; - const bridgeRects = getBridgeRects(railTile.type); - for (const [dx, dy, w, h] of bridgeRects) { - context.fillRect( - x + offsetX + dx / 2, - y + offsetY + dy / 2, - w / 2, - h / 2, - ); - } - context.restore(); - } - - const railRects = getRailroadRects(railTile.type); - for (const [dx, dy, w, h] of railRects) { - context.fillRect( - x + offsetX + dx / 2, - y + offsetY + dy / 2, - w / 2, - h / 2, - ); - } - } - } - } - - private onRailroadSnapEvent(update: RailroadSnapUpdate) { - const original = this.railroads.get(update.originalId); - if (!original) { - console.warn("Could not snap railroad: ", update.originalId); - return; - } - if (!original.isComplete()) { - // The animation is not complete but we don't want to compute where the animation should resume - // Just draw every remaining rails at once - this.drawRemainingTiles(original); - } - - // No need to compute the directions here, the rails are already painted - const directions1: RailTile[] = update.tiles1.map((tile) => ({ - tile, - type: RailType.HORIZONTAL, - })); - const directions2: RailTile[] = update.tiles2.map((tile) => ({ - tile, - type: RailType.HORIZONTAL, - })); - // The rails are already painted, consider them complete - this.railroads.set( - update.newId1, - new RailroadView(update.newId1, directions1, true), - ); - this.railroads.set( - update.newId2, - new RailroadView(update.newId2, directions2, true), - ); - - this.railroads.delete(update.originalId); - } - - private drawRemainingTiles(railroad: RailroadView) { - for (const tile of railroad.remainingTiles()) { - this.paintRail(tile); - } - this.pendingRailroads.delete(railroad.id); - } - - private onRailroadConstruction(railUpdate: RailroadConstructionUpdate) { - const railTiles = computeRailTiles(this.game, railUpdate.tiles); - const rail = new RailroadView(railUpdate.id, railTiles); - this.addRailroad(rail); - } - - private onRailroadDestruction(railUpdate: RailroadDestructionUpdate) { - const railroad = this.railroads.get(railUpdate.id); - if (!railroad) { - console.warn("Can't remove unexisting railroad: ", railUpdate.id); - return; - } - this.removeRailroad(railroad); - } - - private addRailroad(railroad: RailroadView) { - this.railroads.set(railroad.id, railroad); - this.pendingRailroads.add(railroad.id); - } - - private removeRailroad(railroad: RailroadView) { - this.pendingRailroads.delete(railroad.id); - for (const railTile of railroad.drawnTiles()) { - this.clearRailroad(railTile.tile); - this.eventBus.emit(new RailTileChangedEvent(railTile.tile)); - } - this.railroads.delete(railroad.id); - } - - private paintRailTile(railTile: RailTile) { - const currentOwner = this.game.owner(railTile.tile)?.id() ?? null; - const railRef = this.existingRailroads.get(railTile.tile); - - if (railRef) { - railRef.numOccurence++; - railRef.tile = railTile; - railRef.lastOwnerId = currentOwner; - } else { - this.existingRailroads.set(railTile.tile, { - tile: railTile, - numOccurence: 1, - lastOwnerId: currentOwner, - }); - this.railTileIndex.set(railTile.tile, this.railTileList.length); - this.railTileList.push(railTile.tile); - this.paintRail(railTile); - } - } - - private clearRailroad(railroad: TileRef) { - const ref = this.existingRailroads.get(railroad); - if (ref) ref.numOccurence--; - - if (!ref || ref.numOccurence <= 0) { - this.existingRailroads.delete(railroad); - this.removeRailTile(railroad); - if (this.context === undefined) throw new Error("Not initialized"); - if (this.game.isWater(railroad)) { - this.context.clearRect( - this.game.x(railroad) * 2 - 2, - this.game.y(railroad) * 2 - 2, - 5, - 6, - ); - } else { - this.context.clearRect( - this.game.x(railroad) * 2 - 1, - this.game.y(railroad) * 2 - 1, - 3, - 3, - ); - } - } - } - - private removeRailTile(tile: TileRef) { - const idx = this.railTileIndex.get(tile); - if (idx === undefined) return; - - const lastIndex = this.railTileList.length - 1; - const lastTile = this.railTileList[lastIndex]; - - this.railTileList[idx] = lastTile; - this.railTileIndex.set(lastTile, idx); - - this.railTileList.pop(); - this.railTileIndex.delete(tile); - - if (this.nextRailIndexToCheck >= this.railTileList.length) { - this.nextRailIndexToCheck = 0; - } - } - - paintRail(railTile: RailTile) { - if (this.context === undefined) throw new Error("Not initialized"); - const { tile } = railTile; - const { type } = railTile; - const x = this.game.x(tile); - const y = this.game.y(tile); - // If rail tile is over water, paint a bridge underlay first - if (this.game.isWater(tile)) { - this.paintBridge(this.context, x, y, type); - } - const owner = this.game.owner(tile); - const recipient = owner.isPlayer() ? owner : null; - let color = recipient - ? recipient.borderColor() - : colord("rgba(255,255,255,1)"); - - if (this.alternativeView && recipient?.isMe()) { - color = colord("#00ff00"); - } - - this.context.fillStyle = color.toRgbString(); - this.paintRailRects(this.context, x, y, type); - } - - private paintRailRects( - context: CanvasRenderingContext2D, - x: number, - y: number, - direction: RailType, - ) { - const railRects = getRailroadRects(direction); - for (const [dx, dy, w, h] of railRects) { - context.fillRect(x * 2 + dx, y * 2 + dy, w, h); - } - } - - private paintBridge( - context: CanvasRenderingContext2D, - x: number, - y: number, - direction: RailType, - ) { - context.save(); - context.fillStyle = "rgb(197,69,72)"; - const bridgeRects = getBridgeRects(direction); - for (const [dx, dy, w, h] of bridgeRects) { - context.fillRect(x * 2 + dx, y * 2 + dy, w, h); - } - context.restore(); - } -} diff --git a/src/client/graphics/layers/RailroadSprites.ts b/src/client/graphics/layers/RailroadSprites.ts deleted file mode 100644 index 8572ae68fe..0000000000 --- a/src/client/graphics/layers/RailroadSprites.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { RailType } from "./RailroadView"; - -const railTypeToFunctionMap: Record number[][]> = { - [RailType.TOP_RIGHT]: topRightRailroadCornerRects, - [RailType.BOTTOM_LEFT]: bottomLeftRailroadCornerRects, - [RailType.TOP_LEFT]: topLeftRailroadCornerRects, - [RailType.BOTTOM_RIGHT]: bottomRightRailroadCornerRects, - [RailType.HORIZONTAL]: horizontalRailroadRects, - [RailType.VERTICAL]: verticalRailroadRects, -}; - -const railTypeToBridgeFunctionMap: Record number[][]> = { - [RailType.TOP_RIGHT]: topRightBridgeCornerRects, - [RailType.BOTTOM_LEFT]: bottomLeftBridgeCornerRects, - [RailType.TOP_LEFT]: topLeftBridgeCornerRects, - [RailType.BOTTOM_RIGHT]: bottomRightBridgeCornerRects, - [RailType.HORIZONTAL]: horizontalBridge, - [RailType.VERTICAL]: verticalBridge, -}; - -export function getRailroadRects(type: RailType): number[][] { - const railRects = railTypeToFunctionMap[type]; - if (!railRects) { - // Should never happen - throw new Error(`Unsupported RailType: ${type}`); - } - return railRects(); -} - -function horizontalRailroadRects(): number[][] { - // x/y/w/h - const rects = [ - [-1, -1, 2, 1], - [-1, 1, 2, 1], - [-1, 0, 1, 1], - ]; - return rects; -} - -function verticalRailroadRects(): number[][] { - // x/y/w/h - const rects = [ - [-1, -1, 1, 2], - [1, -1, 1, 2], - [0, 0, 1, 1], - ]; - return rects; -} - -function topRightRailroadCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-1, -1, 1, 1], - [0, -1, 1, 2], - [1, -1, 1, 3], - ]; - return rects; -} - -function topLeftRailroadCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-1, -1, 1, 3], - [0, -1, 1, 2], - [1, -1, 1, 1], - ]; - return rects; -} - -function bottomRightRailroadCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-1, 1, 1, 1], - [0, 0, 1, 2], - [1, -1, 1, 3], - ]; - return rects; -} - -function bottomLeftRailroadCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-1, -1, 1, 3], - [0, 0, 1, 2], - [1, 1, 1, 1], - ]; - return rects; -} - -export function getBridgeRects(type: RailType): number[][] { - const bridgeRects = railTypeToBridgeFunctionMap[type]; - if (!bridgeRects) { - // Should never happen - throw new Error(`Unsupported RailType: ${type}`); - } - return bridgeRects(); -} - -function horizontalBridge(): number[][] { - // x/y/w/h - return [ - [-1, -2, 3, 1], - [-1, 2, 3, 1], - [-1, 3, 1, 1], - [1, 3, 1, 1], - ]; -} - -function verticalBridge(): number[][] { - // x/y/w/h - return [ - [-2, -1, 1, 3], - [2, -1, 1, 3], - ]; -} -// ⌞ -function topRightBridgeCornerRects(): number[][] { - return [ - [-2, -2, 1, 2], - [-1, 0, 1, 1], - [0, 1, 1, 1], - [1, 2, 2, 1], - [2, -2, 1, 1], - ]; -} -// ⌝ -function bottomLeftBridgeCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-2, -2, 2, 1], - [0, -1, 1, 1], - [1, 0, 1, 1], - [2, 1, 1, 2], - [-2, 2, 1, 1], - ]; - return rects; -} -// ⌟ -function topLeftBridgeCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-2, -2, 1, 1], - [-2, 2, 2, 1], - [0, 1, 1, 1], - [1, 0, 1, 1], - [2, -2, 1, 2], - ]; - return rects; -} -// ⌜ -function bottomRightBridgeCornerRects(): number[][] { - // x/y/w/h - const rects = [ - [-2, 1, 1, 2], - [-1, 0, 1, 1], - [0, -1, 1, 1], - [1, -2, 2, 1], - [2, 2, 1, 1], - ]; - return rects; -} diff --git a/src/client/graphics/layers/RailroadView.ts b/src/client/graphics/layers/RailroadView.ts deleted file mode 100644 index 2f690965dd..0000000000 --- a/src/client/graphics/layers/RailroadView.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { TileRef } from "../../../core/game/GameMap"; -import { GameView } from "../../../core/game/GameView"; - -export enum RailType { - VERTICAL, - HORIZONTAL, - TOP_LEFT, - TOP_RIGHT, - BOTTOM_LEFT, - BOTTOM_RIGHT, -} - -export type RailTile = { - tile: TileRef; - type: RailType; -}; - -export function computeRailTiles(game: GameView, tiles: TileRef[]): RailTile[] { - if (tiles.length === 0) return []; - if (tiles.length === 1) { - return [{ tile: tiles[0], type: RailType.VERTICAL }]; - } - const railTypes: RailTile[] = []; - // Inverse direction computation for the first tile - railTypes.push({ - tile: tiles[0], - type: computeExtremityDirection(game, tiles[0], tiles[1]), - }); - for (let i = 1; i < tiles.length - 1; i++) { - const direction = computeDirection( - game, - tiles[i - 1], - tiles[i], - tiles[i + 1], - ); - railTypes.push({ tile: tiles[i], type: direction }); - } - railTypes.push({ - tile: tiles[tiles.length - 1], - type: computeExtremityDirection( - game, - tiles[tiles.length - 1], - tiles[tiles.length - 2], - ), - }); - return railTypes; -} - -function computeExtremityDirection( - game: GameView, - tile: TileRef, - next: TileRef, -): RailType { - const x = game.x(tile); - const y = game.y(tile); - const nextX = game.x(next); - const nextY = game.y(next); - - const dx = nextX - x; - const dy = nextY - y; - - if (dx === 0 && dy === 0) return RailType.VERTICAL; // No movement - - if (dx === 0) { - return RailType.VERTICAL; - } else if (dy === 0) { - return RailType.HORIZONTAL; - } - return RailType.VERTICAL; -} - -export function computeDirection( - game: GameView, - prev: TileRef, - current: TileRef, - next: TileRef, -): RailType { - const x1 = game.x(prev); - const y1 = game.y(prev); - const x2 = game.x(current); - const y2 = game.y(current); - const x3 = game.x(next); - const y3 = game.y(next); - - const dx1 = x2 - x1; - const dy1 = y2 - y1; - const dx2 = x3 - x2; - const dy2 = y3 - y2; - - // Straight line - if (dx1 === dx2 && dy1 === dy2) { - if (dx1 !== 0) return RailType.HORIZONTAL; - if (dy1 !== 0) return RailType.VERTICAL; - } - - // Turn (corner) cases - if ((dx1 === 0 && dx2 !== 0) || (dx1 !== 0 && dx2 === 0)) { - // Now figure out which type of corner - if (dx1 === 0 && dx2 === 1 && dy1 === -1) return RailType.BOTTOM_RIGHT; - if (dx1 === 0 && dx2 === -1 && dy1 === -1) return RailType.BOTTOM_LEFT; - if (dx1 === 0 && dx2 === 1 && dy1 === 1) return RailType.TOP_RIGHT; - if (dx1 === 0 && dx2 === -1 && dy1 === 1) return RailType.TOP_LEFT; - - if (dx1 === 1 && dx2 === 0 && dy2 === -1) return RailType.TOP_LEFT; - if (dx1 === -1 && dx2 === 0 && dy2 === -1) return RailType.TOP_RIGHT; - if (dx1 === 1 && dx2 === 0 && dy2 === 1) return RailType.BOTTOM_LEFT; - if (dx1 === -1 && dx2 === 0 && dy2 === 1) return RailType.BOTTOM_RIGHT; - } - return RailType.VERTICAL; -} - -/** - * A list of tile that can be incrementally painted each tick - */ -export class RailroadView { - private headIndex: number = 0; - private tailIndex: number; - private increment: number = 3; - constructor( - public id: number, - private railTiles: RailTile[], - complete: boolean = false, - ) { - // If the railroad is considered complete, no drawing or animation is required - this.tailIndex = complete ? 0 : railTiles.length; - } - - isComplete(): boolean { - return this.headIndex >= this.tailIndex; - } - - tiles(): RailTile[] { - return this.railTiles; - } - - remainingTiles(): RailTile[] { - if (this.isComplete()) { - // Animation complete, no tiles need to be painted - return []; - } - return this.railTiles.slice(this.headIndex, this.tailIndex); - } - - drawnTiles(): RailTile[] { - if (this.isComplete()) { - // Animation complete, every tiles have been painted - return this.tiles(); - } - let drawnTiles = this.railTiles.slice(0, this.headIndex); - drawnTiles = drawnTiles.concat(this.railTiles.slice(this.tailIndex)); - return drawnTiles; - } - - tick(): RailTile[] { - if (this.isComplete()) return []; - let updatedRailTiles: RailTile[]; - // Check if remaining tiles can be done all at once - if (this.tailIndex - this.headIndex <= 2 * this.increment) { - updatedRailTiles = this.railTiles.slice(this.headIndex, this.tailIndex); - } else { - updatedRailTiles = [ - ...this.railTiles.slice( - this.headIndex, - this.headIndex + this.increment, - ), - ...this.railTiles.slice( - this.tailIndex - this.increment, - this.tailIndex, - ), - ]; - } - this.headIndex = Math.min(this.headIndex + this.increment, this.tailIndex); - this.tailIndex = Math.max(this.tailIndex - this.increment, this.headIndex); - return updatedRailTiles; - } -} diff --git a/src/client/graphics/layers/SAMRadiusLayer.ts b/src/client/graphics/layers/SAMRadiusLayer.ts deleted file mode 100644 index a7f0da170e..0000000000 --- a/src/client/graphics/layers/SAMRadiusLayer.ts +++ /dev/null @@ -1,334 +0,0 @@ -import type { EventBus } from "../../../core/EventBus"; -import { UnitType } from "../../../core/game/Game"; -import { GameUpdateType } from "../../../core/game/GameUpdates"; -import type { - GameView, - PlayerView, - UnitView, -} from "../../../core/game/GameView"; -import { ToggleStructureEvent } from "../../InputHandler"; -import { UIState } from "../UIState"; -import { Layer } from "./Layer"; - -type Interval = [number, number]; -interface SAMRadius { - x: number; - y: number; - r: number; - owner: PlayerView; - arcs: Interval[]; -} - -interface SamInfo { - ownerId: number; - level: number; -} -/** - * Layer responsible for rendering SAM launcher defense radii - */ -export class SAMRadiusLayer implements Layer { - private readonly samLaunchers: Map = new Map(); // Track SAM launcher IDs -> SAM info - // track whether the stroke should be shown due to hover or due to an active build ghost - private hoveredShow: boolean = false; - private ghostShow: boolean = false; - private visible: boolean = false; - private samRanges: SAMRadius[] = []; - private dashOffset = 0; - private rotationSpeed = 14; // px per second - private lastRefresh = Date.now(); - private needsRedraw = false; - - private handleToggleStructure(e: ToggleStructureEvent) { - const types = e.structureTypes; - this.hoveredShow = - !!types && - (types.indexOf(UnitType.SAMLauncher) !== -1 || - types.indexOf(UnitType.City) !== -1); - this.updateVisibility(); - } - - constructor( - private readonly game: GameView, - private readonly eventBus: EventBus, - private readonly uiState: UIState, - ) {} - - init() { - // Listen for game updates to detect SAM launcher changes - // Also listen for UI toggle structure events so we can show borders when - // the user is hovering the Atom/Hydrogen option (UnitDisplay emits - // ToggleStructureEvent with SAMLauncher included in the list). - this.eventBus.on(ToggleStructureEvent, (e) => - this.handleToggleStructure(e), - ); - } - - shouldTransform(): boolean { - return true; - } - - tick() { - // Check for updates to SAM launchers - const unitUpdates = this.game.updatesSinceLastTick()?.[GameUpdateType.Unit]; - if (unitUpdates) { - for (const update of unitUpdates) { - const unit = this.game.unit(update.id); - if (unit && unit.type() === UnitType.SAMLauncher) { - if (this.hasChanged(unit)) { - this.needsRedraw = true; // A SAM changed: radiuses shall be recomputed when necessary - break; - } - } - } - } - - // show when in ghost mode for silo/sam/atom/hydrogen - this.ghostShow = - this.uiState.ghostStructure === UnitType.MissileSilo || - this.uiState.ghostStructure === UnitType.SAMLauncher || - this.uiState.ghostStructure === UnitType.City || - this.uiState.ghostStructure === UnitType.AtomBomb || - this.uiState.ghostStructure === UnitType.HydrogenBomb; - this.updateVisibility(); - } - - renderLayer(context: CanvasRenderingContext2D) { - if (this.visible) { - if (this.needsRedraw) { - // SAM changed: the radiuses needs to be updated - this.computeCircleUnions(); - this.needsRedraw = false; - } - this.updateDashAnimation(); - this.drawCirclesUnion(context); - } - } - - private updateDashAnimation() { - const now = Date.now(); - const dt = now - this.lastRefresh; - this.lastRefresh = now; - this.dashOffset += (this.rotationSpeed * dt) / 1000; - if (this.dashOffset > 1e6) this.dashOffset = this.dashOffset % 1000000; - } - - private updateVisibility() { - const next = this.hoveredShow || this.ghostShow; - if (next !== this.visible) { - this.visible = next; - } - } - - private hasChanged(unit: UnitView): boolean { - const samInfos = this.samLaunchers.get(unit.id()); - const isNew = samInfos === undefined; - const active = unit.isActive(); - const ownerId = unit.owner().smallID(); - let hasChanges = isNew || !active; // was built or destroyed - hasChanges ||= !isNew && samInfos.ownerId !== ownerId; // Sam owner changed - hasChanges ||= !isNew && samInfos.level !== unit.level(); // Sam leveled up - return hasChanges; - } - - private getAllSamRanges(): SAMRadius[] { - // Get all active SAM launchers - const samLaunchers = this.game - .units(UnitType.SAMLauncher) - .filter((unit) => unit.isActive()); - - // Update our tracking set - this.samLaunchers.clear(); - samLaunchers.forEach((sam) => - this.samLaunchers.set(sam.id(), { - ownerId: sam.owner().smallID(), - level: sam.level(), - }), - ); - - // Collect radius data - const radiuses = samLaunchers.map((sam) => { - const tile = sam.tile(); - return { - x: this.game.x(tile), - y: this.game.y(tile), - r: this.game.config().samRange(sam.level()), - owner: sam.owner(), - arcs: [], - }; - }); - return radiuses; - } - - private computeUncoveredArcIntervals(a: SAMRadius, circles: SAMRadius[]) { - a.arcs = []; - const TWO_PI = Math.PI * 2; - const EPS = 1e-9; - // helper functions - const normalize = (a: number) => { - while (a < 0) a += TWO_PI; - while (a >= TWO_PI) a -= TWO_PI; - return a; - }; - // merge a list of intervals [s,e] (both between 0..2pi), taking wraparound into account - const mergeIntervals = ( - intervals: Array<[number, number]>, - ): Array<[number, number]> => { - if (intervals.length === 0) return []; - // normalize to non-wrap intervals - const flat: Array<[number, number]> = []; - for (const [s, e] of intervals) { - const ns = normalize(s); - const ne = normalize(e); - if (ne < ns) { - // wraps, split - flat.push([ns, TWO_PI]); - flat.push([0, ne]); - } else { - flat.push([ns, ne]); - } - } - flat.sort((a, b) => a[0] - b[0]); - const merged: Array<[number, number]> = []; - let cur = flat[0].slice() as [number, number]; - for (let i = 1; i < flat.length; i++) { - const it = flat[i]; - if (it[0] <= cur[1] + EPS) { - cur[1] = Math.max(cur[1], it[1]); - } else { - merged.push([cur[0], cur[1]]); - cur = it.slice() as [number, number]; - } - } - merged.push([cur[0], cur[1]]); - return merged; - }; - const covered: Interval[] = []; - let fullyCovered = false; - - for (const b of circles) { - if (a === b) continue; - - // Only same-owner coverage - if (a.owner.smallID() !== b.owner.smallID()) continue; - - const dx = b.x - a.x; - const dy = b.y - a.y; - const d = Math.hypot(dx, dy); - - // a fully inside b - if (d + a.r <= b.r + EPS) { - fullyCovered = true; - break; - } - - // no overlap - if (d >= a.r + b.r - EPS) continue; - - // coincident centers - if (d <= EPS) { - if (b.r >= a.r) { - fullyCovered = true; - break; - } - continue; - } - - // angular span on a covered by b - const theta = Math.atan2(dy, dx); - const cosPhi = (a.r * a.r + d * d - b.r * b.r) / (2 * a.r * d); - const phi = Math.acos(Math.max(-1, Math.min(1, cosPhi))); - - covered.push([theta - phi, theta + phi]); - } - - if (fullyCovered) return; - - const merged = mergeIntervals(covered); - - // subtract from [0, 2π) - const uncovered: Interval[] = []; - if (merged.length === 0) { - uncovered.push([0, TWO_PI]); - } else { - let cursor = 0; - for (const [s, e] of merged) { - if (s > cursor + EPS) { - uncovered.push([cursor, s]); - } - cursor = Math.max(cursor, e); - } - if (cursor < TWO_PI - EPS) { - uncovered.push([cursor, TWO_PI]); - } - } - a.arcs = uncovered; - } - - private drawArcSegments(ctx: CanvasRenderingContext2D, a: SAMRadius) { - const outlineColor = "rgba(0, 0, 0, 1)"; - const lineColorSelf = "rgba(0, 255, 0, 1)"; - const lineColorEnemy = "rgba(255, 0, 0, 1)"; - const lineColorFriend = "rgba(255, 255, 0, 1)"; - const extraOutlineWidth = 1; // adds onto below - const lineWidth = 3; - const lineDash = [12, 6]; - - const offsetX = -this.game.width() / 2; - const offsetY = -this.game.height() / 2; - for (const [s, e] of a.arcs) { - // skip tiny arcs - if (e - s < 1e-3) continue; - ctx.beginPath(); - ctx.arc(a.x + offsetX, a.y + offsetY, a.r, s, e); - - // Outline - ctx.strokeStyle = outlineColor; - ctx.lineWidth = lineWidth + extraOutlineWidth; - ctx.setLineDash([ - lineDash[0] + extraOutlineWidth, - Math.max(lineDash[1] - extraOutlineWidth, 0), - ]); - ctx.lineDashOffset = this.dashOffset + extraOutlineWidth / 2; - ctx.stroke(); - - // Inline - if (a.owner.isMe()) { - ctx.strokeStyle = lineColorSelf; - } else if (this.game.myPlayer()?.isFriendly(a.owner)) { - ctx.strokeStyle = lineColorFriend; - } else { - ctx.strokeStyle = lineColorEnemy; - } - - ctx.lineWidth = lineWidth; - ctx.setLineDash(lineDash); - ctx.lineDashOffset = this.dashOffset; - ctx.stroke(); - } - } - - /** - * Compute for each circle which angular segments are NOT covered by any other circle - */ - private computeCircleUnions() { - this.samRanges = this.getAllSamRanges(); - for (let i = 0; i < this.samRanges.length; i++) { - const a = this.samRanges[i]; - this.computeUncoveredArcIntervals(a, this.samRanges); - } - } - - /** - * Draw union of multiple circles: stroke only the outer arcs so overlapping circles appear as one combined shape. - */ - private drawCirclesUnion(context: CanvasRenderingContext2D) { - const circles = this.samRanges; - if (circles.length === 0 || !this.visible) return; - // Only draw the stroke when UI toggle indicates SAM launchers are focused (e.g. hovering Atom/Hydrogen option). - context.save(); - for (let i = 0; i < circles.length; i++) { - this.drawArcSegments(context, circles[i]); - } - context.restore(); - } -} diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts index 760b17d2fd..a6759c9cb5 100644 --- a/src/client/graphics/layers/StructureIconsLayer.ts +++ b/src/client/graphics/layers/StructureIconsLayer.ts @@ -1,3 +1,11 @@ +/** + * StructureIconsLayer — now just the build ghost + click-to-build flow. + * + * Structure icons themselves are rendered by the WebGL StructurePass; this + * layer keeps the Pixi-based ghost preview (translucent outline at the cursor, + * range circle, price tag) and the build/upgrade event flow. + */ + import { extend } from "colord"; import a11yPlugin from "colord/plugins/a11y"; import { OutlineFilter } from "pixi-filters"; @@ -8,21 +16,16 @@ import { EventBus } from "../../../core/EventBus"; import { wouldNukeBreakAlliance } from "../../../core/execution/Util"; import { BuildableUnit, - Cell, PlayerBuildableUnitType, - PlayerID, - Structures, UnitType, } from "../../../core/game/Game"; import { TileRef } from "../../../core/game/GameMap"; -import { GameUpdateType } from "../../../core/game/GameUpdates"; -import { GameView, UnitView } from "../../../core/game/GameView"; +import { GameView } from "../../../core/game/GameView"; import { ConfirmGhostStructureEvent, GhostStructureChangedEvent, MouseMoveEvent, MouseUpEvent, - ToggleStructureEvent as ToggleStructuresEvent, } from "../../InputHandler"; import { BuildUnitIntentEvent, @@ -33,14 +36,9 @@ import { TransformHandler } from "../TransformHandler"; import { UIState } from "../UIState"; import { Layer } from "./Layer"; import { - DOTS_ZOOM_THRESHOLD, ICON_SCALE_FACTOR_ZOOMED_IN, ICON_SCALE_FACTOR_ZOOMED_OUT, - ICON_SIZE, - LEVEL_SCALE_FACTOR, - OFFSET_ZOOM_Y, SpriteFactory, - STRUCTURE_SHAPES, ZOOM_THRESHOLD, } from "./StructureDrawingUtils"; const bitmapFont = assetUrl("fonts/round_6x6_modified.xml"); @@ -52,19 +50,6 @@ export function shouldPreserveGhostAfterBuild(unitType: UnitType): boolean { extend([a11yPlugin]); -class StructureRenderInfo { - public isOnScreen: boolean = false; - constructor( - public unit: UnitView, - public owner: PlayerID, - public iconContainer: PIXI.Container, - public levelContainer: PIXI.Container, - public dotContainer: PIXI.Container, - public level: number = 0, - public underConstruction: boolean = true, - ) {} -} - export class StructureIconsLayer implements Layer { private ghostUnit: { container: PIXI.Container; @@ -78,34 +63,18 @@ export class StructureIconsLayer implements Layer { buildableUnit: BuildableUnit; } | null = null; private pixicanvas: HTMLCanvasElement; - private iconsStage: PIXI.Container; private ghostStage: PIXI.Container; - private levelsStage: PIXI.Container; private rootStage: PIXI.Container = new PIXI.Container(); - private dotsStage: PIXI.Container; private readonly theme: Theme; private renderer: PIXI.Renderer | null = null; private rendererInitialized: boolean = false; - private readonly rendersByUnitId: Map = - new Map(); - private readonly seenUnitIds: Set = new Set(); private readonly connectedAllySmallIds: Set = new Set(); private readonly mousePos = { x: 0, y: 0 }; - private renderSprites = true; private factory: SpriteFactory; - private readonly structures: Map< - PlayerBuildableUnitType, - { visible: boolean } - > = new Map(Structures.types.map((type) => [type, { visible: true }])); - private lastGhostQueryAt: number; - private visibilityStateDirty = true; + private lastGhostQueryAt: number = 0; private pendingConfirm: MouseUpEvent | null = null; - private hasHiddenStructure = false; private rebuildPending = false; - potentialUpgrade: StructureRenderInfo | undefined; private filterRedArray: OutlineFilter[] = []; - private filterGreenArray: OutlineFilter[] = []; - private filterWhiteArray: OutlineFilter[] = []; constructor( private game: GameView, @@ -114,12 +83,7 @@ export class StructureIconsLayer implements Layer { private transformHandler: TransformHandler, ) { this.theme = game.config().theme(); - this.factory = new SpriteFactory( - this.theme, - game, - transformHandler, - this.renderSprites, - ); + this.factory = new SpriteFactory(this.theme, game, transformHandler, true); } async setupRenderer() { @@ -138,9 +102,6 @@ export class StructureIconsLayer implements Layer { this.pixicanvas.width = window.innerWidth; this.pixicanvas.height = window.innerHeight; - // This will prefer WebGL, eventually WebGPU, and fallback to Canvas - // Restrict using 'preferences: ["WebGPU", "WebGL"]' or - // 'preferences: "WebGPU"' later if needed const renderer = await PIXI.autoDetectRenderer({ canvas: this.pixicanvas, resolution: 1, @@ -152,42 +113,19 @@ export class StructureIconsLayer implements Layer { backgroundColor: 0x00000000, }); - console.info(`Using ${renderer.name} for structure icons layer`); - - this.iconsStage = new PIXI.Container(); - this.iconsStage.position.set(0, 0); - this.iconsStage.setSize(this.pixicanvas.width, this.pixicanvas.height); + console.info(`Using ${renderer.name} for build ghost layer`); this.ghostStage = new PIXI.Container(); this.ghostStage.position.set(0, 0); this.ghostStage.setSize(this.pixicanvas.width, this.pixicanvas.height); - this.levelsStage = new PIXI.Container(); - this.levelsStage.position.set(0, 0); - this.levelsStage.setSize(this.pixicanvas.width, this.pixicanvas.height); - - this.dotsStage = new PIXI.Container(); - this.dotsStage.position.set(0, 0); - this.dotsStage.setSize(this.pixicanvas.width, this.pixicanvas.height); - - this.rootStage.addChild( - this.dotsStage, - this.iconsStage, - this.levelsStage, - this.ghostStage, - ); + this.rootStage.addChild(this.ghostStage); this.rootStage.position.set(0, 0); this.rootStage.setSize(this.pixicanvas.width, this.pixicanvas.height); this.filterRedArray = [ new OutlineFilter({ thickness: 2, color: "rgba(255, 0, 0, 1)" }), ]; - this.filterGreenArray = [ - new OutlineFilter({ thickness: 2, color: "rgba(0, 255, 0, 1)" }), - ]; - this.filterWhiteArray = [ - new OutlineFilter({ thickness: 2, color: "rgb(255, 255, 255)" }), - ]; this.renderer = renderer; @@ -201,8 +139,6 @@ export class StructureIconsLayer implements Layer { if (this.renderer.name === "webgl") { this.renderer.runners.contextChange.add({ - // Listen to contextChange as PixiJS handles WebGL context loss and restores itself. - // Don't listen to "webglcontextrestored" event directly as it can fire before PixiJS is ready. contextChange: () => { requestAnimationFrame(() => { this.redraw(); @@ -214,58 +150,28 @@ export class StructureIconsLayer implements Layer { this.rendererInitialized = true; } - private rebuildAllIcons() { - this.clearGhostStructure(); - this.factory.clearCache(); - const allUnitIds = Array.from(this.seenUnitIds); - this.seenUnitIds.clear(); - for (const unitId of allUnitIds) { - const render = this.rendersByUnitId.get(unitId); - if (render) { - render.iconContainer?.destroy({ children: true }); - render.dotContainer?.destroy({ children: true }); - render.levelContainer?.destroy({ children: true }); - } - const unitView = this.game.unit(unitId); - if (unitView && unitView.isActive()) { - this.handleActiveUnit(unitView); - } else { - this.rendersByUnitId.delete(unitId); - } - } - } - shouldTransform(): boolean { return false; } async redraw() { - if (this.rebuildPending) { - return; - } - if (this.rendererOrGLContextLost()) { - return; - } + if (this.rebuildPending) return; + if (this.rendererOrGLContextLost()) return; this.rebuildPending = true; - try { if (this.renderer?.name === "webgpu") { this.rendererInitialized = false; await this.setupRenderer(); } this.resizeCanvas(); - this.rebuildAllIcons(); + this.clearGhostStructure(); } finally { this.rebuildPending = false; } } async init() { - this.eventBus.on(ToggleStructuresEvent, (e) => - this.toggleStructures(e.structureTypes), - ); this.eventBus.on(MouseMoveEvent, (e) => this.moveGhost(e)); - this.eventBus.on(MouseUpEvent, (e) => this.requestConfirmStructure(e)); this.eventBus.on(ConfirmGhostStructureEvent, () => this.requestConfirmStructure( @@ -281,48 +187,20 @@ export class StructureIconsLayer implements Layer { private rendererOrGLContextLost(): boolean { if (!this.renderer || !this.rendererInitialized) return true; if (this.renderer.name === "webgl") { - // For WebGL, check isLost to prevent ungraceful handling by PixiJS: - // its GL > logPrettyShaderError throws, when getShaderSource returns null - // Needs to be fixed in PixiJS, in meantime prevent it from here return (this.renderer as PIXI.WebGLRenderer).context?.isLost === true; } return false; } resizeCanvas() { - if (this.rendererOrGLContextLost()) { - return; - } + if (this.rendererOrGLContextLost()) return; this.pixicanvas.width = window.innerWidth; this.pixicanvas.height = window.innerHeight; this.renderer?.resize(innerWidth, innerHeight, 1); } - tick() { - const unitUpdates = this.game.updatesSinceLastTick()?.[GameUpdateType.Unit]; - if (unitUpdates) { - for (let i = 0, len = unitUpdates.length; i < len; i++) { - const unitView = this.game.unit(unitUpdates[i].id); - if (unitView === undefined) { - continue; - } - - const unitId = unitView.id(); - if (unitView.isActive()) { - this.handleActiveUnit(unitView); - } else if (this.seenUnitIds.has(unitId)) { - this.handleInactiveUnit(unitView); - } - } - } - this.renderSprites = - this.game.config().userSettings()?.structureSprites() ?? true; - } - renderLayer(mainContext: CanvasRenderingContext2D) { - if (this.rendererOrGLContextLost()) { - return; - } + if (this.rendererOrGLContextLost()) return; if (this.ghostUnit) { if (this.uiState.ghostStructure === null) { @@ -337,18 +215,6 @@ export class StructureIconsLayer implements Layer { } this.renderGhost(); - if (this.transformHandler.hasChanged()) { - for (const render of this.rendersByUnitId.values()) { - this.computeNewLocation(render); - } - } - const scale = this.transformHandler.scale; - - this.dotsStage!.visible = scale <= DOTS_ZOOM_THRESHOLD; - this.iconsStage!.visible = - scale > DOTS_ZOOM_THRESHOLD && - (scale <= ZOOM_THRESHOLD || !this.renderSprites); - this.levelsStage!.visible = scale > ZOOM_THRESHOLD && this.renderSprites; if (this.renderer) { this.renderer.render(this.rootStage); mainContext.drawImage(this.renderer.canvas, 0, 0); @@ -359,9 +225,7 @@ export class StructureIconsLayer implements Layer { if (!this.ghostUnit) return; const now = performance.now(); - if (now - this.lastGhostQueryAt < 50) { - return; - } + if (now - this.lastGhostQueryAt < 50) return; this.lastGhostQueryAt = now; let tileRef: TileRef | undefined; const tile = this.transformHandler.screenToWorldCoordinates( @@ -373,7 +237,6 @@ export class StructureIconsLayer implements Layer { } // Check if targeting an ally (for nuke warning visual) - // Uses shared logic with NukeExecution.maybeBreakAlliances() let targetingAlly = false; const myPlayer = this.game.myPlayer(); const nukeType = this.ghostUnit.buildableUnit.type; @@ -382,7 +245,6 @@ export class StructureIconsLayer implements Layer { myPlayer && (nukeType === UnitType.AtomBomb || nukeType === UnitType.HydrogenBomb) ) { - // Only check connected allies - nuking disconnected allies doesn't cause a traitor debuff this.connectedAllySmallIds.clear(); const allies = myPlayer.allies(); for (let i = 0; i < allies.length; i++) { @@ -407,10 +269,6 @@ export class StructureIconsLayer implements Layer { ?.myPlayer() ?.buildables(tileRef, [this.ghostUnit?.buildableUnit.type]) .then((buildables) => { - if (this.potentialUpgrade) { - this.potentialUpgrade.iconContainer.filters = []; - this.potentialUpgrade.dotContainer.filters = []; - } if (this.ghostUnit?.container) { this.ghostUnit.container.filters = []; } @@ -442,18 +300,6 @@ export class StructureIconsLayer implements Layer { this.updateGhostRange(targetLevel, targetingAlly); if (unit.canUpgrade) { - this.potentialUpgrade = this.rendersByUnitId.get(unit.canUpgrade); - if ( - this.potentialUpgrade && - this.potentialUpgrade.unit.owner().id() !== - this.game.myPlayer()?.id() - ) { - this.potentialUpgrade = undefined; - } - if (this.potentialUpgrade) { - this.potentialUpgrade.iconContainer.filters = this.filterGreenArray; - this.potentialUpgrade.dotContainer.filters = this.filterGreenArray; - } // No overlapping when a structure is upgradable this.uiState.overlappingRailroads = []; this.uiState.ghostRailPaths = []; @@ -511,21 +357,12 @@ export class StructureIconsLayer implements Layer { .fill({ color: 0x000000, alpha: 0.65 }); } - /** - * True when the ghost exists and buildableUnit has been refreshed (canBuild or canUpgrade set). - * Used to avoid running createStructure before renderGhost's async buildables() has updated the ghost. - */ private isGhostReadyForConfirm(): boolean { if (!this.ghostUnit) return false; const bu = this.ghostUnit.buildableUnit; return bu.canBuild !== false || bu.canUpgrade !== false; } - /** - * Request confirm (place/upgrade): run createStructure now if ghost is ready, otherwise defer until - * renderGhost's buildables() callback has updated the ghost. Shared by Enter (ConfirmGhostStructureEvent) - * and mouse click (MouseUpEvent) so numpad-select-then-confirm works. - */ private requestConfirmStructure(e: MouseUpEvent): void { if (!this.ghostUnit && !this.uiState.ghostStructure) return; if (this.isGhostReadyForConfirm()) { @@ -587,9 +424,7 @@ export class StructureIconsLayer implements Layer { private createGhostStructure(type: PlayerBuildableUnitType | null) { const player = this.game.myPlayer(); if (!player) return; - if (type === null) { - return; - } + if (type === null) return; const local = this.transformHandler.screenToCanvasCoordinates( this.mousePos.x, this.mousePos.y, @@ -629,11 +464,6 @@ export class StructureIconsLayer implements Layer { this.ghostUnit.range?.destroy({ children: true }); this.ghostUnit = null; } - if (this.potentialUpgrade) { - this.potentialUpgrade.iconContainer.filters = []; - this.potentialUpgrade.dotContainer.filters = []; - this.potentialUpgrade = undefined; - } this.uiState.ghostRailPaths = []; } @@ -646,9 +476,7 @@ export class StructureIconsLayer implements Layer { private resolveGhostRangeLevel( buildableUnit: BuildableUnit, ): number | undefined { - if (buildableUnit.type !== UnitType.SAMLauncher) { - return undefined; - } + if (buildableUnit.type !== UnitType.SAMLauncher) return undefined; if (buildableUnit.canUpgrade !== false) { const existing = this.game.unit(buildableUnit.canUpgrade); if (existing) { @@ -657,14 +485,11 @@ export class StructureIconsLayer implements Layer { console.error("Failed to find existing SAMLauncher for upgrade"); } } - return 1; } private updateGhostRange(level?: number, targetingAlly: boolean = false) { - if (!this.ghostUnit) { - return; - } + if (!this.ghostUnit) return; if ( this.ghostUnit.range && @@ -691,242 +516,4 @@ export class StructureIconsLayer implements Layer { this.ghostUnit.range = range; } } - - private toggleStructures( - toggleStructureType: PlayerBuildableUnitType[] | null, - ): void { - for (const [structureType, infos] of this.structures) { - infos.visible = - toggleStructureType?.indexOf(structureType) !== -1 || - toggleStructureType === null; - } - this.visibilityStateDirty = true; - for (const render of this.rendersByUnitId.values()) { - this.modifyVisibility(render); - } - } - - private refreshVisibilityStateCache() { - if (!this.visibilityStateDirty) { - return; - } - - this.hasHiddenStructure = false; - for (const infos of this.structures.values()) { - if (infos.visible === false) { - this.hasHiddenStructure = true; - break; - } - } - - this.visibilityStateDirty = false; - } - - private findRenderByUnit( - unitView: UnitView, - ): StructureRenderInfo | undefined { - return this.rendersByUnitId.get(unitView.id()); - } - - private handleActiveUnit(unitView: UnitView) { - if (this.seenUnitIds.has(unitView.id())) { - const render = this.findRenderByUnit(unitView); - if (render) { - this.checkForConstructionState(render, unitView); - this.checkForDeletionState(render, unitView); - this.checkForOwnershipChange(render, unitView); - this.checkForLevelChange(render, unitView); - } - } else if ( - this.structures.has(unitView.type() as PlayerBuildableUnitType) - ) { - this.addNewStructure(unitView); - } - } - - private handleInactiveUnit(unitView: UnitView) { - if (!this.seenUnitIds.has(unitView.id())) { - return; - } - - const render = this.findRenderByUnit(unitView); - if (render) { - this.deleteStructure(render); - } - } - - private modifyVisibility(render: StructureRenderInfo) { - this.refreshVisibilityStateCache(); - - const structureType = render.unit.type() as PlayerBuildableUnitType; - const structureInfos = this.structures.get(structureType); - - if (structureInfos) { - render.iconContainer.alpha = structureInfos.visible ? 1 : 0.3; - render.dotContainer.alpha = structureInfos.visible ? 1 : 0.3; - if (structureInfos.visible && this.hasHiddenStructure) { - render.iconContainer.filters = this.filterWhiteArray; - render.dotContainer.filters = this.filterWhiteArray; - } else { - render.iconContainer.filters = []; - render.dotContainer.filters = []; - } - } - } - - private checkForDeletionState(render: StructureRenderInfo, unit: UnitView) { - if (unit.markedForDeletion() !== false) { - render.iconContainer?.destroy({ children: true }); - render.dotContainer?.destroy({ children: true }); - render.iconContainer = this.createIconSprite(unit); - render.dotContainer = this.createDotSprite(unit); - this.modifyVisibility(render); - } - } - - private checkForConstructionState( - render: StructureRenderInfo, - unit: UnitView, - ) { - if (render.underConstruction && !unit.isUnderConstruction()) { - render.underConstruction = false; - render.iconContainer?.destroy({ children: true }); - render.dotContainer?.destroy({ children: true }); - render.iconContainer = this.createIconSprite(unit); - render.dotContainer = this.createDotSprite(unit); - this.modifyVisibility(render); - } - } - - private checkForOwnershipChange(render: StructureRenderInfo, unit: UnitView) { - if (render.owner !== unit.owner().id()) { - render.owner = unit.owner().id(); - render.iconContainer?.destroy({ children: true }); - render.dotContainer?.destroy({ children: true }); - render.iconContainer = this.createIconSprite(unit); - render.dotContainer = this.createDotSprite(unit); - this.modifyVisibility(render); - } - } - - private checkForLevelChange(render: StructureRenderInfo, unit: UnitView) { - if (render.level !== unit.level()) { - render.level = unit.level(); - render.iconContainer?.destroy({ children: true }); - render.levelContainer?.destroy({ children: true }); - render.dotContainer?.destroy({ children: true }); - render.iconContainer = this.createIconSprite(unit); - render.levelContainer = this.createLevelSprite(unit); - render.dotContainer = this.createDotSprite(unit); - this.modifyVisibility(render); - } - } - - private computeNewLocation(render: StructureRenderInfo) { - const tile = render.unit.tile(); - const worldPos = new Cell(this.game.x(tile), this.game.y(tile)); - const screenPos = this.transformHandler.worldToCanvasCoordinates(worldPos); - screenPos.x = Math.round(screenPos.x); - - const scale = this.transformHandler.scale; - screenPos.y = Math.round( - scale >= ZOOM_THRESHOLD && - this.game.config().userSettings()?.structureSprites() - ? screenPos.y - scale * OFFSET_ZOOM_Y - : screenPos.y, - ); - - const type = render.unit.type(); - const margin = - type !== undefined && STRUCTURE_SHAPES[type] !== undefined - ? ICON_SIZE[STRUCTURE_SHAPES[type]] - : 28; - - const onScreen = - screenPos.x + margin > 0 && - screenPos.x - margin < this.pixicanvas.width && - screenPos.y + margin > 0 && - screenPos.y - margin < this.pixicanvas.height; - - if (onScreen) { - if (scale > ZOOM_THRESHOLD) { - const target = this.game.config().userSettings()?.structureSprites() - ? render.levelContainer - : render.iconContainer; - target.position.set(screenPos.x, screenPos.y); - target.scale.set( - Math.max( - 1, - scale / - (target === render.levelContainer - ? LEVEL_SCALE_FACTOR - : ICON_SCALE_FACTOR_ZOOMED_IN), - ), - ); - } else if (scale > DOTS_ZOOM_THRESHOLD) { - render.iconContainer.position.set(screenPos.x, screenPos.y); - render.iconContainer.scale.set( - Math.min(1, scale / ICON_SCALE_FACTOR_ZOOMED_OUT), - ); - } else { - render.dotContainer.position.set(screenPos.x, screenPos.y); - } - } - - if (render.isOnScreen !== onScreen) { - render.isOnScreen = onScreen; - render.iconContainer.visible = onScreen; - render.dotContainer.visible = onScreen; - render.levelContainer.visible = onScreen; - } - } - - private addNewStructure(unitView: UnitView) { - this.seenUnitIds.add(unitView.id()); - const render = new StructureRenderInfo( - unitView, - unitView.owner().id(), - this.createIconSprite(unitView), - this.createLevelSprite(unitView), - this.createDotSprite(unitView), - unitView.level(), - unitView.isUnderConstruction(), - ); - this.rendersByUnitId.set(unitView.id(), render); - this.computeNewLocation(render); - this.modifyVisibility(render); - } - - private createLevelSprite(unit: UnitView): PIXI.Container { - return this.factory.createUnitContainer(unit, { - type: "level", - stage: this.levelsStage, - }); - } - - private createDotSprite(unit: UnitView): PIXI.Container { - return this.factory.createUnitContainer(unit, { - type: "dot", - stage: this.dotsStage, - }); - } - - private createIconSprite(unit: UnitView): PIXI.Container { - return this.factory.createUnitContainer(unit, { - type: "icon", - stage: this.iconsStage, - }); - } - - private deleteStructure(render: StructureRenderInfo) { - render.iconContainer?.destroy({ children: true }); - render.levelContainer?.destroy({ children: true }); - render.dotContainer?.destroy({ children: true }); - const unitId = render.unit.id(); - this.rendersByUnitId.delete(unitId); - this.seenUnitIds.delete(unitId); - if (this.potentialUpgrade?.unit.id() === unitId) { - this.potentialUpgrade = undefined; - } - } } diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts deleted file mode 100644 index 9ba8e3b2a8..0000000000 --- a/src/client/graphics/layers/StructureLayer.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { colord, Colord } from "colord"; -import { Theme } from "src/core/configuration/Theme"; -import { assetUrl } from "../../../core/AssetUrls"; -import { EventBus } from "../../../core/EventBus"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; - -import { Cell, UnitType } from "../../../core/game/Game"; -import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap"; -import { GameUpdateType } from "../../../core/game/GameUpdates"; -import { GameView, UnitView } from "../../../core/game/GameView"; -const cityIcon = assetUrl("images/buildings/cityAlt1.png"); -const factoryIcon = assetUrl("images/buildings/factoryAlt1.png"); -const shieldIcon = assetUrl("images/buildings/fortAlt3.png"); -const anchorIcon = assetUrl("images/buildings/port1.png"); -const missileSiloIcon = assetUrl("images/buildings/silo1.png"); -const SAMMissileIcon = assetUrl("images/buildings/silo4.png"); - -const underConstructionColor = colord("rgb(150,150,150)"); - -// Base radius values and scaling factor for unit borders and territories -const BASE_BORDER_RADIUS = 16.5; -const BASE_TERRITORY_RADIUS = 13.5; -const RADIUS_SCALE_FACTOR = 0.5; -const ZOOM_THRESHOLD = 4.3; // below this zoom level, structures are not rendered - -interface UnitRenderConfig { - icon: string; - borderRadius: number; - territoryRadius: number; -} - -export class StructureLayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D; - private unitIcons: Map = new Map(); - private theme: Theme; - private tempCanvas: HTMLCanvasElement; - private tempContext: CanvasRenderingContext2D; - - // Configuration for supported unit types only - private readonly unitConfigs: Partial> = { - [UnitType.Port]: { - icon: anchorIcon, - borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR, - territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR, - }, - [UnitType.City]: { - icon: cityIcon, - borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR, - territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR, - }, - [UnitType.Factory]: { - icon: factoryIcon, - borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR, - territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR, - }, - [UnitType.MissileSilo]: { - icon: missileSiloIcon, - borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR, - territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR, - }, - [UnitType.DefensePost]: { - icon: shieldIcon, - borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR, - territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR, - }, - [UnitType.SAMLauncher]: { - icon: SAMMissileIcon, - borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR, - territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR, - }, - }; - - constructor( - private game: GameView, - private eventBus: EventBus, - private transformHandler: TransformHandler, - ) { - this.theme = game.config().theme(); - this.tempCanvas = document.createElement("canvas"); - const tempContext = this.tempCanvas.getContext("2d"); - if (tempContext === null) throw new Error("2d context not supported"); - this.tempContext = tempContext; - this.loadIconData(); - } - - private loadIcon(unitType: string, config: UnitRenderConfig) { - const image = new Image(); - // crossOrigin must be set before src so the fetch is CORS-checked. - // Without this, an icon served from CDN_BASE taints any canvas/texture - // it's drawn into, and WebGL refuses to upload it via texImage2D. - image.crossOrigin = "anonymous"; - image.src = config.icon; - image.onload = () => { - this.unitIcons.set(unitType, image); - console.log( - `icon loaded: ${unitType}, size: ${image.width}x${image.height}`, - ); - }; - image.onerror = () => { - console.error(`Failed to load icon for ${unitType}: ${config.icon}`); - }; - } - - private loadIconData() { - Object.entries(this.unitConfigs).forEach(([unitType, config]) => { - this.loadIcon(unitType, config); - }); - } - - shouldTransform(): boolean { - return true; - } - - tick() { - const updates = this.game.updatesSinceLastTick(); - const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : []; - for (const u of unitUpdates) { - const unit = this.game.unit(u.id); - if (unit === undefined) continue; - this.handleUnitRendering(unit); - } - } - - init() { - this.redraw(); - } - - redraw() { - console.log("structure layer redrawing"); - this.canvas = document.createElement("canvas"); - const context = this.canvas.getContext("2d", { alpha: true }); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - - // Firefox's GPU limit is 8192, only known browser issue - const maxTextureSize = 8192; - const scaleX = maxTextureSize / this.game.width(); - const scaleY = maxTextureSize / this.game.height(); - const targetScale = Math.min(2, scaleX, scaleY); - this.canvas.width = Math.max( - 1, - Math.floor(this.game.width() * targetScale), - ); - this.canvas.height = Math.max( - 1, - Math.floor(this.game.height() * targetScale), - ); - - // Enable smooth scaling - this.context.imageSmoothingEnabled = true; - this.context.imageSmoothingQuality = "high"; - this.context.scale( - this.canvas.width / (this.game.width() * 2), - this.canvas.height / (this.game.height() * 2), - ); - - Promise.all( - Array.from(this.unitIcons.values()).map((img) => - img.decode?.().catch((err) => { - console.warn("Failed to decode unit icon image:", err); - }), - ), - ).finally(() => { - this.game.units().forEach((u) => this.handleUnitRendering(u)); - }); - } - - renderLayer(context: CanvasRenderingContext2D) { - if ( - this.transformHandler.scale <= ZOOM_THRESHOLD || - !this.game.config().userSettings()?.structureSprites() - ) { - return; - } - context.drawImage( - this.canvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - } - - private isUnitTypeSupported(unitType: UnitType): boolean { - return unitType in this.unitConfigs; - } - - private drawBorder( - unit: UnitView, - borderColor: Colord, - config: UnitRenderConfig, - ) { - // Draw border and territory - for (const tile of this.game.bfs( - unit.tile(), - isometricDistFN(unit.tile(), config.borderRadius, true), - )) { - this.paintCell( - new Cell(this.game.x(tile), this.game.y(tile)), - borderColor, - 255, - ); - } - - for (const tile of this.game.bfs( - unit.tile(), - isometricDistFN(unit.tile(), config.territoryRadius, true), - )) { - this.paintCell( - new Cell(this.game.x(tile), this.game.y(tile)), - unit.isUnderConstruction() - ? underConstructionColor - : unit.owner().territoryColor(), - 130, - ); - } - } - - private handleUnitRendering(unit: UnitView) { - const unitType = unit.type(); - const iconType = unitType; - if (!this.isUnitTypeSupported(unitType)) return; - - const config = this.unitConfigs[unitType]; - let icon: HTMLImageElement | undefined; - let borderColor = unit.owner().borderColor(); - - // Handle cooldown states and special icons - if (unit.isUnderConstruction()) { - icon = this.unitIcons.get(iconType); - borderColor = underConstructionColor; - } else { - icon = this.unitIcons.get(iconType); - } - - if (!config || !icon) return; - - // Clear previous rendering - for (const tile of this.game.bfs( - unit.tile(), - euclDistFN(unit.tile(), config.borderRadius + 1, true), - )) { - this.clearCell(new Cell(this.game.x(tile), this.game.y(tile))); - } - - if (!unit.isActive()) return; - - this.drawBorder(unit, borderColor, config); - - // Render icon at 1/2 scale for better quality - const scaledWidth = icon.width >> 1; - const scaledHeight = icon.height >> 1; - const startX = this.game.x(unit.tile()) - (scaledWidth >> 1); - const startY = this.game.y(unit.tile()) - (scaledHeight >> 1); - - this.renderIcon(icon, startX, startY - 4, scaledWidth, scaledHeight, unit); - } - - private renderIcon( - image: HTMLImageElement, - startX: number, - startY: number, - width: number, - height: number, - unit: UnitView, - ) { - let color = unit.owner().borderColor(); - if (unit.isUnderConstruction()) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - color = underConstructionColor; - } - - // Make temp canvas at the final render size (2x scale) - this.tempCanvas.width = width * 2; - this.tempCanvas.height = height * 2; - - // Enable smooth scaling - this.tempContext.imageSmoothingEnabled = true; - this.tempContext.imageSmoothingQuality = "high"; - - // Draw the image at final size with high quality scaling - this.tempContext.drawImage(image, 0, 0, width * 2, height * 2); - - // Restore the alpha channel - this.tempContext.globalCompositeOperation = "destination-in"; - this.tempContext.drawImage(image, 0, 0, width * 2, height * 2); - - // Draw the final result to the main canvas - this.context.drawImage(this.tempCanvas, startX * 2, startY * 2); - } - - paintCell(cell: Cell, color: Colord, alpha: number) { - this.clearCell(cell); - this.context.fillStyle = color.alpha(alpha / 255).toRgbString(); - this.context.fillRect(cell.x * 2, cell.y * 2, 2, 2); - } - - clearCell(cell: Cell) { - this.context.clearRect(cell.x * 2, cell.y * 2, 2, 2); - } -} diff --git a/src/client/graphics/layers/TerrainLayer.ts b/src/client/graphics/layers/TerrainLayer.ts deleted file mode 100644 index 492c970289..0000000000 --- a/src/client/graphics/layers/TerrainLayer.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Theme } from "src/core/configuration/Theme"; -import { Config } from "../../../core/configuration/Config"; -import { GameView } from "../../../core/game/GameView"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; - -export class TerrainLayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D; - private imageData: ImageData; - private theme: Theme; - private config: Config; - - constructor( - private game: GameView, - private transformHandler: TransformHandler, - ) { - this.config = this.game.config(); - } - shouldTransform(): boolean { - return true; - } - tick() { - if (this.config.theme() !== this.theme) { - this.redraw(); - return; - } - // Repaint terrain for tiles whose terrain changed (e.g. nuke - // turning land to water). - const updatedTiles = this.game.recentlyUpdatedTerrainTiles(); - if (updatedTiles.length > 0) { - let dirty = false; - for (const tile of updatedTiles) { - const terrainColor = this.theme.terrainColor(this.game, tile); - const offset = tile * 4; - const r = terrainColor.rgba.r; - const g = terrainColor.rgba.g; - const b = terrainColor.rgba.b; - if ( - this.imageData.data[offset] !== r || - this.imageData.data[offset + 1] !== g || - this.imageData.data[offset + 2] !== b - ) { - this.imageData.data[offset] = r; - this.imageData.data[offset + 1] = g; - this.imageData.data[offset + 2] = b; - dirty = true; - } - } - if (dirty) { - this.context.putImageData(this.imageData, 0, 0); - } - } - } - - init() { - console.log("redrew terrain layer"); - this.redraw(); - } - - redraw(): void { - this.canvas = document.createElement("canvas"); - this.canvas.width = this.game.width(); - this.canvas.height = this.game.height(); - - const context = this.canvas.getContext("2d", { alpha: false }); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - - this.imageData = this.context.createImageData( - this.canvas.width, - this.canvas.height, - ); - - this.initImageData(); - this.context.putImageData(this.imageData, 0, 0); - } - - initImageData() { - this.theme = this.config.theme(); - this.game.forEachTile((tile) => { - const terrainColor = this.theme.terrainColor(this.game, tile); - // TODO: isn't tileref and index the same? - const index = this.game.y(tile) * this.game.width() + this.game.x(tile); - const offset = index * 4; - this.imageData.data[offset] = terrainColor.rgba.r; - this.imageData.data[offset + 1] = terrainColor.rgba.g; - this.imageData.data[offset + 2] = terrainColor.rgba.b; - this.imageData.data[offset + 3] = 255; - }); - } - - renderLayer(context: CanvasRenderingContext2D) { - if (this.transformHandler.scale < 1) { - context.imageSmoothingEnabled = true; - context.imageSmoothingQuality = "low"; - } else { - context.imageSmoothingEnabled = false; - } - context.drawImage( - this.canvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - } -} diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts deleted file mode 100644 index 3cc3d34e69..0000000000 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ /dev/null @@ -1,709 +0,0 @@ -import { PriorityQueue } from "@datastructures-js/priority-queue"; -import { Colord } from "colord"; -import { Theme } from "src/core/configuration/Theme"; -import { EventBus } from "../../../core/EventBus"; -import { - Cell, - ColoredTeams, - PlayerType, - Team, - UnitType, -} from "../../../core/game/Game"; -import { euclDistFN, TileRef } from "../../../core/game/GameMap"; -import { GameUpdateType } from "../../../core/game/GameUpdates"; -import { GameView, PlayerView } from "../../../core/game/GameView"; -import { PseudoRandom } from "../../../core/PseudoRandom"; -import { - AlternateViewEvent, - DragEvent, - MouseOverEvent, -} from "../../InputHandler"; -import { FrameProfiler } from "../FrameProfiler"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; - -export class TerritoryLayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D; - private imageData: ImageData; - private alternativeImageData: ImageData; - private borderAnimTime = 0; - - private cachedTerritoryPatternsEnabled: boolean | undefined; - - private tileToRenderQueue: PriorityQueue<{ - tile: TileRef; - lastUpdate: number; - }> = new PriorityQueue((a, b) => { - return a.lastUpdate - b.lastUpdate; - }); - private random = new PseudoRandom(123); - private theme: Theme; - - // Used for spawn highlighting - private highlightCanvas: HTMLCanvasElement; - private highlightContext: CanvasRenderingContext2D; - - private highlightedTerritory: PlayerView | null = null; - - private alternativeView = false; - private lastDragTime = 0; - private nodrawDragDuration = 200; - private lastMousePosition: { x: number; y: number } | null = null; - - private refreshRate = 10; //refresh every 10ms - private lastRefresh = 0; - - private lastFocusedPlayer: PlayerView | null = null; - - constructor( - private game: GameView, - private eventBus: EventBus, - private transformHandler: TransformHandler, - ) { - this.theme = game.config().theme(); - this.cachedTerritoryPatternsEnabled = undefined; - } - - shouldTransform(): boolean { - return true; - } - - async paintPlayerBorder(player: PlayerView) { - const tiles = await player.borderTiles(); - tiles.borderTiles.forEach((tile: TileRef) => { - this.paintTerritory(tile, true); // Immediately paint the tile instead of enqueueing - }); - } - - tick() { - if (this.game.inSpawnPhase()) { - this.spawnHighlight(); - } - - this.game.recentlyUpdatedTiles().forEach((t) => { - this.enqueueTile(t); - // Immediately clear territory overlay for water tiles so old - // borders/territory don't persist visually (e.g. after nuke turns land to water) - if (this.game.isWater(t)) { - this.clearTile(t); - } - }); - const updates = this.game.updatesSinceLastTick(); - const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : []; - unitUpdates.forEach((update) => { - if (update.unitType === UnitType.DefensePost) { - // Only update borders if the defense post is not under construction - if (update.underConstruction) { - return; // Skip barrier creation while under construction - } - - const tile = update.pos; - this.game - .bfs(tile, euclDistFN(tile, this.game.config().defensePostRange())) - .forEach((t) => { - if ( - this.game.isBorder(t) && - (this.game.ownerID(t) === update.ownerID || - this.game.ownerID(t) === update.lastOwnerID) - ) { - this.enqueueTile(t); - } - }); - } - }); - - // Detect alliance mutations - const myPlayer = this.game.myPlayer(); - if (myPlayer) { - updates?.[GameUpdateType.BrokeAlliance]?.forEach((update) => { - const territory = this.game.playerBySmallID(update.betrayedID); - if (territory && territory instanceof PlayerView) { - this.redrawBorder(territory); - } - }); - - updates?.[GameUpdateType.AllianceRequestReply]?.forEach((update) => { - if ( - update.accepted && - (update.request.requestorID === myPlayer.smallID() || - update.request.recipientID === myPlayer.smallID()) - ) { - const territoryId = - update.request.requestorID === myPlayer.smallID() - ? update.request.recipientID - : update.request.requestorID; - const territory = this.game.playerBySmallID(territoryId); - if (territory && territory instanceof PlayerView) { - this.redrawBorder(territory); - } - } - }); - updates?.[GameUpdateType.EmbargoEvent]?.forEach((update) => { - const player = this.game.playerBySmallID(update.playerID) as PlayerView; - const embargoed = this.game.playerBySmallID( - update.embargoedID, - ) as PlayerView; - - if ( - player.id() === myPlayer?.id() || - embargoed.id() === myPlayer?.id() - ) { - this.redrawBorder(player, embargoed); - } - }); - } - - const focusedPlayer = this.game.focusedPlayer(); - if (focusedPlayer !== this.lastFocusedPlayer) { - if (this.lastFocusedPlayer) { - this.paintPlayerBorder(this.lastFocusedPlayer); - } - if (focusedPlayer) { - this.paintPlayerBorder(focusedPlayer); - } - this.lastFocusedPlayer = focusedPlayer; - } - } - - private spawnHighlight() { - this.highlightContext.clearRect( - 0, - 0, - this.game.width(), - this.game.height(), - ); - - this.drawFocusedPlayerHighlight(); - - const humans = this.game - .playerViews() - .filter((p) => p.type() === PlayerType.Human); - - const focusedPlayer = this.game.focusedPlayer(); - const teamColors = Object.values(ColoredTeams); - for (const human of humans) { - if (human === focusedPlayer) { - continue; - } - const center = human.nameLocation(); - if (!center) { - continue; - } - const centerTile = this.game.ref(center.x, center.y); - if (!centerTile) { - continue; - } - let color = this.theme.spawnHighlightColor(); - const myPlayer = this.game.myPlayer(); - if (myPlayer !== null && myPlayer !== human && myPlayer.team() === null) { - // In FFA games (when team === null), use default yellow spawn highlight color - color = this.theme.spawnHighlightColor(); - } else if (myPlayer !== null && myPlayer !== human) { - // In Team games, the spawn highlight color becomes that player's team color - // Optionally, this could be broken down to teammate or enemy and simplified to green and red, respectively - const team = human.team(); - if (team !== null && teamColors.includes(team)) { - color = this.theme.teamColor(team); - } else { - if (myPlayer.isFriendly(human)) { - color = this.theme.spawnHighlightTeamColor(); - } else { - color = this.theme.spawnHighlightColor(); - } - } - } - - for (const tile of this.game.bfs( - centerTile, - euclDistFN(centerTile, 9, true), - )) { - if (!this.game.hasOwner(tile)) { - this.paintHighlightTile(tile, color, 255); - } - } - } - } - - private drawFocusedPlayerHighlight() { - const focusedPlayer = this.game.focusedPlayer(); - - if (!focusedPlayer) { - return; - } - const center = focusedPlayer.nameLocation(); - if (!center) { - return; - } - // Breathing border animation - this.borderAnimTime += 0.5; - const minRad = 8; - const maxRad = 24; - // Range: [minPadding..maxPadding] - const radius = - minRad + (maxRad - minRad) * (0.5 + 0.5 * Math.sin(this.borderAnimTime)); - - const baseColor = this.theme.spawnHighlightSelfColor(); //white - let teamColor: Colord; - - const team: Team | null = focusedPlayer.team(); - if (team !== null && Object.values(ColoredTeams).includes(team)) { - teamColor = this.theme.teamColor(team).alpha(0.5); - } else { - teamColor = baseColor; - } - - this.drawBreathingRing( - center.x, - center.y, - minRad, - maxRad, - radius, - baseColor, // Always draw white static semi-transparent ring - teamColor, // Pass the breathing ring color. White for FFA, Duos, Trios, Quads. Transparent team color for TEAM games. - ); - - // Draw breathing rings for teammates in team games (helps colorblind players identify teammates) - this.drawTeammateHighlights(minRad, maxRad, radius); - } - - private drawTeammateHighlights( - minRad: number, - maxRad: number, - radius: number, - ) { - const myPlayer = this.game.myPlayer(); - if (myPlayer === null || myPlayer.team() === null) { - return; - } - - const teammates = this.game - .playerViews() - .filter((p) => p !== myPlayer && myPlayer.isOnSameTeam(p)); - - // Smaller radius for teammates (more subtle than self highlight) - const teammateMinRad = 5; - const teammateMaxRad = 14; - const teammateRadius = - teammateMinRad + - (teammateMaxRad - teammateMinRad) * - ((radius - minRad) / (maxRad - minRad)); - - const teamColors = Object.values(ColoredTeams); - for (const teammate of teammates) { - const center = teammate.nameLocation(); - if (!center) { - continue; - } - - const team = teammate.team(); - let baseColor: Colord; - let breathingColor: Colord; - - if (team !== null && teamColors.includes(team)) { - baseColor = this.theme.teamColor(team).alpha(0.5); - breathingColor = this.theme.teamColor(team).alpha(0.5); - } else { - baseColor = this.theme.spawnHighlightTeamColor(); - breathingColor = this.theme.spawnHighlightTeamColor(); - } - - this.drawBreathingRing( - center.x, - center.y, - teammateMinRad, - teammateMaxRad, - teammateRadius, - baseColor, - breathingColor, - ); - } - } - - init() { - this.eventBus.on(MouseOverEvent, (e) => this.onMouseOver(e)); - this.eventBus.on(AlternateViewEvent, (e) => { - this.alternativeView = e.alternateView; - }); - this.eventBus.on(DragEvent, (e) => { - // TODO: consider re-enabling this on mobile or low end devices for smoother dragging. - // this.lastDragTime = Date.now(); - }); - this.redraw(); - } - - onMouseOver(event: MouseOverEvent) { - this.lastMousePosition = { x: event.x, y: event.y }; - this.updateHighlightedTerritory(); - } - - private updateHighlightedTerritory() { - if (!this.alternativeView) { - return; - } - - if (!this.lastMousePosition) { - return; - } - - const cell = this.transformHandler.screenToWorldCoordinates( - this.lastMousePosition.x, - this.lastMousePosition.y, - ); - if (!this.game.isValidCoord(cell.x, cell.y)) { - return; - } - - const previousTerritory = this.highlightedTerritory; - const territory = this.getTerritoryAtCell(cell); - - if (territory) { - this.highlightedTerritory = territory; - } else { - this.highlightedTerritory = null; - } - - if (previousTerritory?.id() !== this.highlightedTerritory?.id()) { - const territories: PlayerView[] = []; - if (previousTerritory) { - territories.push(previousTerritory); - } - if (this.highlightedTerritory) { - territories.push(this.highlightedTerritory); - } - this.redrawBorder(...territories); - } - } - - private getTerritoryAtCell(cell: { x: number; y: number }) { - const tile = this.game.ref(cell.x, cell.y); - if (!tile) { - return null; - } - // If the tile has no owner, it is either a fallout tile or a terra nullius tile. - if (!this.game.hasOwner(tile)) { - return null; - } - const owner = this.game.owner(tile); - return owner instanceof PlayerView ? owner : null; - } - - redraw() { - console.log("redrew territory layer"); - this.canvas = document.createElement("canvas"); - const context = this.canvas.getContext("2d"); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - this.canvas.width = this.game.width(); - this.canvas.height = this.game.height(); - - this.imageData = this.context.getImageData( - 0, - 0, - this.canvas.width, - this.canvas.height, - ); - this.alternativeImageData = this.context.getImageData( - 0, - 0, - this.canvas.width, - this.canvas.height, - ); - this.initImageData(); - - this.context.putImageData( - this.alternativeView ? this.alternativeImageData : this.imageData, - 0, - 0, - ); - - // Add a second canvas for highlights - this.highlightCanvas = document.createElement("canvas"); - const highlightContext = this.highlightCanvas.getContext("2d", { - alpha: true, - }); - if (highlightContext === null) throw new Error("2d context not supported"); - this.highlightContext = highlightContext; - this.highlightCanvas.width = this.game.width(); - this.highlightCanvas.height = this.game.height(); - - this.game.forEachTile((t) => { - this.paintTerritory(t); - }); - } - - redrawBorder(...players: PlayerView[]) { - return Promise.all( - players.map(async (player) => { - const tiles = await player.borderTiles(); - tiles.borderTiles.forEach((tile: TileRef) => { - this.paintTerritory(tile, true); - }); - }), - ); - } - - initImageData() { - this.game.forEachTile((tile) => { - const cell = new Cell(this.game.x(tile), this.game.y(tile)); - const index = cell.y * this.game.width() + cell.x; - const offset = index * 4; - this.imageData.data[offset + 3] = 0; - this.alternativeImageData.data[offset + 3] = 0; - }); - } - - renderLayer(context: CanvasRenderingContext2D) { - const now = Date.now(); - if ( - now > this.lastDragTime + this.nodrawDragDuration && - now > this.lastRefresh + this.refreshRate - ) { - this.lastRefresh = now; - const renderTerritoryStart = FrameProfiler.start(); - this.renderTerritory(); - FrameProfiler.end("TerritoryLayer:renderTerritory", renderTerritoryStart); - - const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect(); - const vx0 = Math.max(0, topLeft.x); - const vy0 = Math.max(0, topLeft.y); - const vx1 = Math.min(this.game.width() - 1, bottomRight.x); - const vy1 = Math.min(this.game.height() - 1, bottomRight.y); - - const w = vx1 - vx0 + 1; - const h = vy1 - vy0 + 1; - - if (w > 0 && h > 0) { - const putImageStart = FrameProfiler.start(); - this.context.putImageData( - this.alternativeView ? this.alternativeImageData : this.imageData, - 0, - 0, - vx0, - vy0, - w, - h, - ); - FrameProfiler.end("TerritoryLayer:putImageData", putImageStart); - } - } - - const drawCanvasStart = FrameProfiler.start(); - context.drawImage( - this.canvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - FrameProfiler.end("TerritoryLayer:drawCanvas", drawCanvasStart); - if (this.game.inSpawnPhase()) { - const highlightDrawStart = FrameProfiler.start(); - context.drawImage( - this.highlightCanvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - FrameProfiler.end( - "TerritoryLayer:drawHighlightCanvas", - highlightDrawStart, - ); - } - } - - renderTerritory() { - let numToRender = Math.floor(this.tileToRenderQueue.size() / 10); - if (numToRender === 0 || this.game.inSpawnPhase()) { - numToRender = this.tileToRenderQueue.size(); - } - - while (numToRender > 0) { - numToRender--; - - const entry = this.tileToRenderQueue.pop(); - if (!entry) { - break; - } - - const tile = entry.tile; - this.paintTerritory(tile); - for (const neighbor of this.game.neighbors(tile)) { - this.paintTerritory(neighbor, true); - } - } - } - - paintTerritory(tile: TileRef, isBorder: boolean = false) { - if (isBorder && !this.game.hasOwner(tile)) { - return; - } - - if (!this.game.hasOwner(tile)) { - if (this.game.hasFallout(tile)) { - this.paintTile(this.imageData, tile, this.theme.falloutColor(), 150); - this.paintTile( - this.alternativeImageData, - tile, - this.theme.falloutColor(), - 150, - ); - return; - } - this.clearTile(tile); - return; - } - const owner = this.game.owner(tile) as PlayerView; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const isHighlighted = - this.highlightedTerritory && - this.highlightedTerritory.id() === owner.id(); - const myPlayer = this.game.myPlayer(); - - if (this.game.isBorder(tile)) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const playerIsFocused = owner && this.game.focusedPlayer() === owner; - if (myPlayer) { - const alternativeColor = this.alternateViewColor(owner); - this.paintTile(this.alternativeImageData, tile, alternativeColor, 255); - } - const isDefended = this.game.hasUnitNearby( - tile, - this.game.config().defensePostRange(), - UnitType.DefensePost, - owner.id(), - ); - - this.paintTile( - this.imageData, - tile, - owner.borderColor(tile, isDefended), - 255, - ); - } else { - // Alternative view only shows borders. - this.clearAlternativeTile(tile); - - this.paintTile(this.imageData, tile, owner.territoryColor(tile), 150); - } - } - - alternateViewColor(other: PlayerView): Colord { - const myPlayer = this.game.myPlayer(); - if (!myPlayer) { - return this.theme.neutralColor(); - } - if (other.smallID() === myPlayer.smallID()) { - return this.theme.selfColor(); - } - if (other.isFriendly(myPlayer)) { - return this.theme.allyColor(); - } - if (!other.hasEmbargo(myPlayer)) { - return this.theme.neutralColor(); - } - return this.theme.enemyColor(); - } - - paintAlternateViewTile(tile: TileRef, other: PlayerView) { - const color = this.alternateViewColor(other); - this.paintTile(this.alternativeImageData, tile, color, 255); - } - - paintTile(imageData: ImageData, tile: TileRef, color: Colord, alpha: number) { - const offset = tile * 4; - imageData.data[offset] = color.rgba.r; - imageData.data[offset + 1] = color.rgba.g; - imageData.data[offset + 2] = color.rgba.b; - imageData.data[offset + 3] = alpha; - } - - clearTile(tile: TileRef) { - const offset = tile * 4; - this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) - this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) - } - - clearAlternativeTile(tile: TileRef) { - const offset = tile * 4; - this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) - } - - enqueueTile(tile: TileRef) { - this.tileToRenderQueue.push({ - tile: tile, - lastUpdate: this.game.ticks() + this.random.nextFloat(0, 0.5), - }); - } - - async enqueuePlayerBorder(player: PlayerView) { - const playerBorderTiles = await player.borderTiles(); - playerBorderTiles.borderTiles.forEach((tile: TileRef) => { - this.enqueueTile(tile); - }); - } - - paintHighlightTile(tile: TileRef, color: Colord, alpha: number) { - this.clearTile(tile); - const x = this.game.x(tile); - const y = this.game.y(tile); - this.highlightContext.fillStyle = color.alpha(alpha / 255).toRgbString(); - this.highlightContext.fillRect(x, y, 1, 1); - } - - clearHighlightTile(tile: TileRef) { - const x = this.game.x(tile); - const y = this.game.y(tile); - this.highlightContext.clearRect(x, y, 1, 1); - } - - private drawBreathingRing( - cx: number, - cy: number, - minRad: number, - maxRad: number, - radius: number, - transparentColor: Colord, - breathingColor: Colord, - ) { - const ctx = this.highlightContext; - if (!ctx) return; - - // Draw a semi-transparent ring around the starting location - ctx.beginPath(); - // Transparency matches the highlight color provided - const transparent = transparentColor.alpha(0); - const radGrad = ctx.createRadialGradient(cx, cy, minRad, cx, cy, maxRad); - - // Pixels with radius < minRad are transparent - radGrad.addColorStop(0, transparent.toRgbString()); - // The ring then starts with solid highlight color - radGrad.addColorStop(0.01, transparentColor.toRgbString()); - radGrad.addColorStop(0.1, transparentColor.toRgbString()); - // The outer edge of the ring is transparent - radGrad.addColorStop(1, transparent.toRgbString()); - - // Draw an arc at the max radius and fill with the created radial gradient - ctx.arc(cx, cy, maxRad, 0, Math.PI * 2); - ctx.fillStyle = radGrad; - ctx.closePath(); - ctx.fill(); - - const breatheInner = breathingColor.alpha(0); - // Draw a solid ring around the starting location with outer radius = the breathing radius - ctx.beginPath(); - const radGrad2 = ctx.createRadialGradient(cx, cy, minRad, cx, cy, radius); - // Pixels with radius < minRad are transparent - radGrad2.addColorStop(0, breatheInner.toRgbString()); - // The ring then starts with solid highlight color - radGrad2.addColorStop(0.01, breathingColor.toRgbString()); - // The ring is solid throughout - radGrad2.addColorStop(1, breathingColor.toRgbString()); - - // Draw an arc at the current breathing radius and fill with the created "gradient" - ctx.arc(cx, cy, radius, 0, Math.PI * 2); - ctx.fillStyle = radGrad2; - ctx.fill(); - } -} diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 4c4ff15bb6..1338baad45 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -2,7 +2,6 @@ import { Colord } from "colord"; import { Theme } from "src/core/configuration/Theme"; import { EventBus } from "../../../core/EventBus"; import { UnitType } from "../../../core/game/Game"; -import { GameUpdateType } from "../../../core/game/GameUpdates"; import { GameView, UnitView } from "../../../core/game/GameView"; import { CloseViewEvent, @@ -11,34 +10,19 @@ import { WarshipSelectionBoxCompleteEvent, WarshipSelectionBoxUpdateEvent, } from "../../InputHandler"; -import { ProgressBar } from "../ProgressBar"; import { TransformHandler } from "../TransformHandler"; import { Layer } from "./Layer"; -const COLOR_PROGRESSION = [ - "rgb(232, 25, 25)", - "rgb(240, 122, 25)", - "rgb(202, 231, 15)", - "rgb(44, 239, 18)", -]; -const HEALTHBAR_WIDTH = 11; // Width of the health bar -const LOADINGBAR_WIDTH = 14; // Width of the loading bar -const PROGRESSBAR_HEIGHT = 3; // Height of a bar - /** - * Layer responsible for drawing UI elements that overlay the game - * such as selection boxes, health bars, etc. + * Layer responsible for drawing UI elements that overlay the game. + * Currently: warship selection boxes + drag-rectangle selection. + * Health/progress bars are now drawn by the WebGL BarPass. */ export class UILayer implements Layer { private canvas: HTMLCanvasElement; private context: CanvasRenderingContext2D | null; private theme: Theme | null = null; private selectionAnimTime = 0; - private allProgressBars: Map< - number, - { unit: UnitView; progressBar: ProgressBar } - > = new Map(); - private allHealthBars: Map = new Map(); // Keep track of currently selected unit private selectedUnit: UnitView | null = null; @@ -109,15 +93,6 @@ export class UILayer implements Layer { this.multiSelectedWarships = this.multiSelectedWarships.filter((u) => u.isActive(), ); - - this.game - .updatesSinceLastTick() - ?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id)) - ?.forEach((unitView) => { - if (unitView === undefined) return; - this.onUnitEvent(unitView); - }); - this.updateProgressBars(); } init() { @@ -249,56 +224,6 @@ export class UILayer implements Layer { this.selectionBoxCtx = this.selectionBoxCanvas.getContext("2d"); } - onUnitEvent(unit: UnitView) { - const underConst = unit.isUnderConstruction(); - if (underConst) { - this.createLoadingBar(unit); - return; - } - switch (unit.type()) { - case UnitType.Warship: { - this.drawHealthBar(unit); - break; - } - case UnitType.City: - case UnitType.Factory: - case UnitType.DefensePost: - case UnitType.Port: - case UnitType.MissileSilo: - case UnitType.SAMLauncher: - if ( - unit.markedForDeletion() !== false || - unit.missileReadinesss() < 1 - ) { - this.createLoadingBar(unit); - } - break; - default: - return; - } - } - - private clearIcon(icon: HTMLImageElement, startX: number, startY: number) { - if (this.context !== null) { - this.context.clearRect(startX, startY, icon.width, icon.height); - } - } - - private drawIcon( - icon: HTMLImageElement, - unit: UnitView, - startX: number, - startY: number, - ) { - if (this.context === null || this.theme === null) { - return; - } - const color = unit.owner().borderColor(); - this.context.fillStyle = color.toRgbString(); - this.context.fillRect(startX, startY, icon.width, icon.height); - this.context.drawImage(icon, startX, startY); - } - /** * Handle the unit selection event (single or multi). * When event.units.length > 0 it's a multi-selection from box/select-all. @@ -458,117 +383,6 @@ export class UILayer implements Layer { }; } - /** - * Draw health bar for a unit - */ - public drawHealthBar(unit: UnitView) { - const maxHealth = this.game.unitInfo(unit.type()).maxHealth; - if (maxHealth === undefined || this.context === null) { - return; - } - if ( - this.allHealthBars.has(unit.id()) && - (unit.health() >= maxHealth || unit.health() <= 0 || !unit.isActive()) - ) { - // full hp/dead warships dont need a hp bar - this.allHealthBars.get(unit.id())?.clear(); - this.allHealthBars.delete(unit.id()); - } else if ( - unit.isActive() && - unit.health() < maxHealth && - unit.health() > 0 - ) { - this.allHealthBars.get(unit.id())?.clear(); - const healthBar = new ProgressBar( - COLOR_PROGRESSION, - this.context, - this.game.x(unit.tile()) - 4, - this.game.y(unit.tile()) - 6, - HEALTHBAR_WIDTH, - PROGRESSBAR_HEIGHT, - unit.health() / maxHealth, - ); - // keep track of units that have health bars for clearing purposes - this.allHealthBars.set(unit.id(), healthBar); - } - } - - private updateProgressBars() { - this.allProgressBars.forEach((progressBarInfo, unitId) => { - const progress = this.getProgress(progressBarInfo.unit); - if (progress >= 1) { - this.allProgressBars.get(unitId)?.progressBar.clear(); - this.allProgressBars.delete(unitId); - return; - } else { - progressBarInfo.progressBar.setProgress(progress); - } - }); - } - - private getProgress(unit: UnitView): number { - if (!unit.isActive()) { - return 1; - } - const underConst = unit.isUnderConstruction(); - if (underConst) { - const constDuration = this.game.unitInfo( - unit.type(), - ).constructionDuration; - if (constDuration === undefined) { - throw new Error("unit does not have constructionTime"); - } - return ( - (this.game.ticks() - unit.createdAt()) / - (constDuration === 0 ? 1 : constDuration) - ); - } - switch (unit.type()) { - case UnitType.MissileSilo: - case UnitType.SAMLauncher: - return !unit.markedForDeletion() - ? unit.missileReadinesss() - : this.deletionProgress(this.game, unit); - case UnitType.City: - case UnitType.Factory: - case UnitType.Port: - case UnitType.DefensePost: - return this.deletionProgress(this.game, unit); - default: - return 1; - } - } - - private deletionProgress(game: GameView, unit: UnitView): number { - const deleteAt = unit.markedForDeletion(); - if (deleteAt === false) return 1; - return Math.max( - 0, - (deleteAt - game.ticks()) / game.config().deletionMarkDuration(), - ); - } - - public createLoadingBar(unit: UnitView) { - if (!this.context) { - return; - } - if (!this.allProgressBars.has(unit.id())) { - const progressBar = new ProgressBar( - COLOR_PROGRESSION, - this.context, - this.game.x(unit.tile()) - 6, - this.game.y(unit.tile()) + 6, - LOADINGBAR_WIDTH, - PROGRESSBAR_HEIGHT, - 0, - ); - this.allProgressBars.set(unit.id(), { - unit, - progressBar, - }); - } - } - paintCell(x: number, y: number, color: Colord, alpha: number) { if (this.context === null) throw new Error("null context"); this.clearCell(x, y); diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts deleted file mode 100644 index e40fb4d0ec..0000000000 --- a/src/client/graphics/layers/UnitLayer.ts +++ /dev/null @@ -1,768 +0,0 @@ -import { colord, Colord } from "colord"; -import { Theme } from "src/core/configuration/Theme"; -import { EventBus } from "../../../core/EventBus"; -import { Cell, UnitType } from "../../../core/game/Game"; -import { TileRef } from "../../../core/game/GameMap"; -import { GameView, UnitView } from "../../../core/game/GameView"; -import { BezenhamLine } from "../../../core/utilities/Line"; -import { - AlternateViewEvent, - CloseViewEvent, - ContextMenuEvent, - MouseUpEvent, - SelectAllWarshipsEvent, - TouchEvent, - UnitSelectionEvent, - WarshipSelectionBoxCancelEvent, - WarshipSelectionBoxCompleteEvent, -} from "../../InputHandler"; -import { MoveWarshipIntentEvent } from "../../Transport"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; - -import { GameUpdateType } from "../../../core/game/GameUpdates"; -import { - getColoredSprite, - isSpriteReady, - loadAllSprites, -} from "../SpriteLoader"; - -enum Relationship { - Self, - Ally, - Enemy, -} - -export class UnitLayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D; - private transportShipTrailCanvas: HTMLCanvasElement; - private unitTrailContext: CanvasRenderingContext2D; - - private unitToTrail = new Map(); - private pendingTrailClears: UnitView[] = []; - - private theme: Theme; - - private alternateView = false; - - private oldShellTile = new Map(); - - private transformHandler: TransformHandler; - - // Selected unit property as suggested in the review comment - private selectedUnit: UnitView | null = null; - - // Multi-selected warships (from selection box) - private selectedWarships: UnitView[] = []; - - // Configuration for unit selection - private readonly WARSHIP_SELECTION_RADIUS = 10; // Radius in game cells for warship selection hit zone - - constructor( - private game: GameView, - private eventBus: EventBus, - transformHandler: TransformHandler, - ) { - this.theme = game.config().theme(); - this.transformHandler = transformHandler; - } - - shouldTransform(): boolean { - return true; - } - - tick() { - const updatedUnitIds = - this.game - .updatesSinceLastTick() - ?.[GameUpdateType.Unit]?.map((unit) => unit.id) ?? []; - - const motionPlanUnitIds = this.game.motionPlannedUnitIds(); - - if (updatedUnitIds.length === 0) { - this.updateUnitsSprites(motionPlanUnitIds); - return; - } - if (motionPlanUnitIds.length === 0) { - this.updateUnitsSprites(updatedUnitIds); - return; - } - - const unitIds = new Set(updatedUnitIds); - for (const id of motionPlanUnitIds) { - unitIds.add(id); - } - this.updateUnitsSprites(Array.from(unitIds)); - } - - init() { - this.eventBus.on(AlternateViewEvent, (e) => this.onAlternativeViewEvent(e)); - this.eventBus.on(MouseUpEvent, (e) => this.onMouseUp(e)); - this.eventBus.on(TouchEvent, (e) => this.onTouch(e)); - this.eventBus.on(UnitSelectionEvent, (e) => this.onUnitSelectionChange(e)); - this.eventBus.on(WarshipSelectionBoxCompleteEvent, (e) => - this.onSelectionBoxComplete(e), - ); - this.eventBus.on(WarshipSelectionBoxCancelEvent, () => - this.onSelectionBoxCancel(), - ); - this.eventBus.on(CloseViewEvent, () => this.onSelectionBoxCancel()); - this.eventBus.on(SelectAllWarshipsEvent, () => this.onSelectAllWarships()); - this.redraw(); - - loadAllSprites(); - } - - /** - * Find player-owned warships near the given cell within a configurable radius - * @param clickRef The tile to check - * @returns Array of player's warships in range, sorted by distance (closest first) - */ - private findWarshipsNearCell(clickRef: TileRef): UnitView[] { - // Only select warships owned by the player - return this.game - .units(UnitType.Warship) - .filter( - (unit) => - unit.isActive() && - unit.owner() === this.game.myPlayer() && // Only allow selecting own warships - this.game.manhattanDist(unit.tile(), clickRef) <= - this.WARSHIP_SELECTION_RADIUS, - ) - .sort((a, b) => { - // Sort by distance (closest first) - const distA = this.game.manhattanDist(a.tile(), clickRef); - const distB = this.game.manhattanDist(b.tile(), clickRef); - return distA - distB; - }); - } - - private onMouseUp( - event: MouseUpEvent, - clickRef?: TileRef, - nearbyWarships?: UnitView[], - ) { - if (clickRef === undefined) { - // Convert screen coordinates to world coordinates - const cell = this.transformHandler.screenToWorldCoordinates( - event.x, - event.y, - ); - if (!this.game.isValidCoord(cell.x, cell.y)) return; - - clickRef = this.game.ref(cell.x, cell.y); - } - if (!this.game.isWater(clickRef)) return; - - // If we have multi-selected warships, send them all to this tile - if (this.selectedWarships.length > 0) { - const myPlayer = this.game.myPlayer(); - const activeIds = this.selectedWarships - .filter((u) => u.isActive() && u.owner() === myPlayer) - .map((u) => u.id()); - - if (activeIds.length > 0) { - this.eventBus.emit(new MoveWarshipIntentEvent(activeIds, clickRef)); - } - this.selectedWarships = []; - this.eventBus.emit(new UnitSelectionEvent(null, false)); - return; - } - - if (this.selectedUnit) { - this.eventBus.emit( - new MoveWarshipIntentEvent([this.selectedUnit.id()], clickRef), - ); - // Deselect - this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false)); - return; - } - - // Find warships near this tile, sorted by distance - nearbyWarships ??= this.findWarshipsNearCell(clickRef); - if (nearbyWarships.length > 0) { - // Toggle selection of the closest warship - this.eventBus.emit(new UnitSelectionEvent(nearbyWarships[0], true)); - } - } - - private onTouch(event: TouchEvent) { - const cell = this.transformHandler.screenToWorldCoordinates( - event.x, - event.y, - ); - - if (!this.game.isValidCoord(cell.x, cell.y)) { - return; - } - - const clickRef = this.game.ref(cell.x, cell.y); - if (this.game.inSpawnPhase()) { - // No Radial Menu during spawn phase, only spawn point selection - if (!this.game.isWater(clickRef)) { - this.eventBus.emit(new MouseUpEvent(event.x, event.y)); - } - return; - } - - if (!this.game.isWater(clickRef)) { - // No warship to find because no Ocean tile, open Radial Menu - this.eventBus.emit(new ContextMenuEvent(event.x, event.y)); - return; - } - - if (this.selectedUnit) { - // Reuse the mouse logic, send clickRef to avoid fetching it again - this.onMouseUp(new MouseUpEvent(event.x, event.y), clickRef); - return; - } - - // Also delegate if we have multi-selected warships - if (this.selectedWarships.length > 0) { - this.onMouseUp(new MouseUpEvent(event.x, event.y), clickRef); - return; - } - - const nearbyWarships = this.findWarshipsNearCell(clickRef); - - if (nearbyWarships.length > 0) { - this.onMouseUp( - new MouseUpEvent(event.x, event.y), - clickRef, - nearbyWarships, - ); - } else { - // No warships selected or nearby, open Radial Menu - this.eventBus.emit(new ContextMenuEvent(event.x, event.y)); - } - } - - /** - * Handle unit selection changes - */ - private onUnitSelectionChange(event: UnitSelectionEvent) { - if (event.isSelected) { - this.selectedUnit = event.unit; - } else if (this.selectedUnit === event.unit) { - this.selectedUnit = null; - } - } - - /** - * Handle completion of shift+drag selection box. - * Finds all player-owned warships within the screen rectangle. - */ - private onSelectionBoxComplete(event: WarshipSelectionBoxCompleteEvent) { - const x1 = Math.min(event.startX, event.endX); - const y1 = Math.min(event.startY, event.endY); - const x2 = Math.max(event.startX, event.endX); - const y2 = Math.max(event.startY, event.endY); - - const myPlayer = this.game.myPlayer(); - if (!myPlayer) return; - - this.selectedWarships = this.game.units(UnitType.Warship).filter((unit) => { - if (!unit.isActive() || unit.owner() !== myPlayer) return false; - const screen = this.transformHandler.worldToScreenCoordinates( - new Cell(this.game.x(unit.tile()), this.game.y(unit.tile())), - ); - return ( - screen.x >= x1 && screen.x <= x2 && screen.y >= y1 && screen.y <= y2 - ); - }); - - // Clear single selection if we got a box selection - if (this.selectedWarships.length > 0 && this.selectedUnit) { - this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false)); - } - - // Notify UILayer to draw selection boxes for all selected warships - this.eventBus.emit( - new UnitSelectionEvent(null, true, this.selectedWarships), - ); - } - - private onSelectionBoxCancel() { - this.selectedWarships = []; - this.eventBus.emit(new UnitSelectionEvent(null, false)); - } - - private onSelectAllWarships() { - const myPlayer = this.game.myPlayer(); - if (!myPlayer) return; - - const allWarships = this.game - .units(UnitType.Warship) - .filter((u) => u.isActive() && u.owner() === myPlayer); - - if (allWarships.length === 0) return; - - // Clear single selection if active - if (this.selectedUnit) { - this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false)); - } - - this.selectedWarships = allWarships; - this.eventBus.emit( - new UnitSelectionEvent(null, true, this.selectedWarships), - ); - } - - /** - * Handle unit deactivation or destruction - * If the selected unit is removed from the game, deselect it - */ - private handleUnitDeactivation(unit: UnitView) { - if (this.selectedUnit === unit && !unit.isActive()) { - this.eventBus.emit(new UnitSelectionEvent(unit, false)); - } - } - - renderLayer(context: CanvasRenderingContext2D) { - context.drawImage( - this.transportShipTrailCanvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - context.drawImage( - this.canvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - } - - onAlternativeViewEvent(event: AlternateViewEvent) { - this.alternateView = event.alternateView; - this.redraw(); - } - - redraw() { - this.canvas = document.createElement("canvas"); - const context = this.canvas.getContext("2d"); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - this.transportShipTrailCanvas = document.createElement("canvas"); - const trailContext = this.transportShipTrailCanvas.getContext("2d"); - if (trailContext === null) throw new Error("2d context not supported"); - this.unitTrailContext = trailContext; - - this.canvas.width = this.game.width(); - this.canvas.height = this.game.height(); - this.transportShipTrailCanvas.width = this.game.width(); - this.transportShipTrailCanvas.height = this.game.height(); - - this.updateUnitsSprites(this.game.units().map((unit) => unit.id())); - - this.unitToTrail.forEach((trail, unit) => { - for (const t of trail) { - this.paintCell( - this.game.x(t), - this.game.y(t), - this.relationship(unit), - unit.owner().territoryColor(), - 150, - this.unitTrailContext, - ); - } - }); - } - - private updateUnitsSprites(unitIds: number[]) { - const unitsToUpdate = unitIds - ?.map((id) => this.game.unit(id)) - .filter((unit) => unit !== undefined); - - if (unitsToUpdate) { - // the clearing and drawing of unit sprites need to be done in 2 passes - // otherwise the sprite of a unit can be drawn on top of another unit - this.clearUnitsCells(unitsToUpdate); - this.drawUnitsCells(unitsToUpdate); - this.flushTrailClears(); - } - } - - private clearUnitsCells(unitViews: UnitView[]) { - unitViews - .filter((unitView) => isSpriteReady(unitView)) - .forEach((unitView) => { - const sprite = getColoredSprite(unitView, this.theme); - const clearsize = sprite.width + 1; - const lastX = this.game.x(unitView.lastTile()); - const lastY = this.game.y(unitView.lastTile()); - this.context.clearRect( - lastX - clearsize / 2, - lastY - clearsize / 2, - clearsize, - clearsize, - ); - }); - } - - private drawUnitsCells(unitViews: UnitView[]) { - unitViews.forEach((unitView) => this.onUnitEvent(unitView)); - } - - private relationship(unit: UnitView): Relationship { - const myPlayer = this.game.myPlayer(); - if (myPlayer === null) { - return Relationship.Enemy; - } - if (myPlayer === unit.owner()) { - return Relationship.Self; - } - if (myPlayer.isFriendly(unit.owner())) { - return Relationship.Ally; - } - return Relationship.Enemy; - } - - onUnitEvent(unit: UnitView) { - // Check if unit was deactivated - if (!unit.isActive()) { - this.handleUnitDeactivation(unit); - } - - switch (unit.type()) { - case UnitType.TransportShip: - this.handleBoatEvent(unit); - break; - case UnitType.Warship: - this.handleWarShipEvent(unit); - break; - case UnitType.Shell: - this.handleShellEvent(unit); - break; - case UnitType.SAMMissile: - this.handleMissileEvent(unit); - break; - case UnitType.TradeShip: - this.handleTradeShipEvent(unit); - break; - case UnitType.Train: - this.handleTrainEvent(unit); - break; - case UnitType.MIRVWarhead: - this.handleMIRVWarhead(unit); - break; - case UnitType.AtomBomb: - case UnitType.HydrogenBomb: - case UnitType.MIRV: - this.handleNuke(unit); - break; - } - } - - private handleWarShipEvent(unit: UnitView) { - if (unit.warshipState().state !== "patrolling" && unit.isActive()) { - if (unit.warshipState().isInCombat) { - this.drawSprite(unit, colord("rgb(200,0,0)")); - } else { - this.drawSprite(unit); - } - this.drawRetreatCross(unit); - return; - } - - if (unit.warshipState().isInCombat) { - this.drawSprite(unit, colord("rgb(200,0,0)")); - return; - } - - this.drawSprite(unit); - } - - private drawRetreatCross(unit: UnitView) { - // Blink: 500ms on, 500ms off - if (Math.floor(Date.now() / 500) % 2 === 0) return; - const x = this.game.x(unit.tile()); - const y = this.game.y(unit.tile()); - const ctx = this.context; - ctx.save(); - const cx = x + 0.5; - const cy = y + 0.5; - ctx.lineCap = "square"; - ctx.strokeStyle = "rgb(36,36,36)"; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(cx, cy - 1.5); - ctx.lineTo(cx, cy + 1.5); - ctx.moveTo(cx - 1.5, cy); - ctx.lineTo(cx + 1.5, cy); - ctx.stroke(); - ctx.restore(); - } - - private handleShellEvent(unit: UnitView) { - const rel = this.relationship(unit); - - // Clear current and previous positions - this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile())); - const oldTile = this.oldShellTile.get(unit); - if (oldTile !== undefined) { - this.clearCell(this.game.x(oldTile), this.game.y(oldTile)); - } - - this.oldShellTile.set(unit, unit.lastTile()); - if (!unit.isActive()) { - return; - } - - // Paint current and previous positions - this.paintCell( - this.game.x(unit.tile()), - this.game.y(unit.tile()), - rel, - unit.owner().borderColor(), - 255, - ); - this.paintCell( - this.game.x(unit.lastTile()), - this.game.y(unit.lastTile()), - rel, - unit.owner().borderColor(), - 255, - ); - } - - // interception missile from SAM - private handleMissileEvent(unit: UnitView) { - this.drawSprite(unit); - } - - private drawTrail(trail: number[], color: Colord, rel: Relationship) { - // Paint new trail - for (const t of trail) { - this.paintCell( - this.game.x(t), - this.game.y(t), - rel, - color, - 150, - this.unitTrailContext, - ); - } - } - - private flushTrailClears() { - if (this.pendingTrailClears.length === 0) return; - - const clearedTiles = new Set(); - for (const unit of this.pendingTrailClears) { - const trail = this.unitToTrail.get(unit); - if (trail) { - for (const t of trail) { - if (!clearedTiles.has(t)) { - this.clearCell( - this.game.x(t), - this.game.y(t), - this.unitTrailContext, - ); - clearedTiles.add(t); - } - } - this.unitToTrail.delete(unit); - } - } - this.pendingTrailClears = []; - - // Single repaint pass for all remaining units - for (const [other, trail] of this.unitToTrail) { - const rel = this.relationship(other); - for (const t of trail) { - if (clearedTiles.has(t)) { - this.paintCell( - this.game.x(t), - this.game.y(t), - rel, - other.owner().territoryColor(), - 150, - this.unitTrailContext, - ); - } - } - } - } - - private handleNuke(unit: UnitView) { - const rel = this.relationship(unit); - - if (!this.unitToTrail.has(unit)) { - this.unitToTrail.set(unit, []); - } - - let newTrailSize = 1; - const trail = this.unitToTrail.get(unit) ?? []; - // It can move faster than 1 pixel, draw a line for the trail or else it will be dotted - if (trail.length >= 1) { - const cur = { - x: this.game.x(unit.lastTile()), - y: this.game.y(unit.lastTile()), - }; - const prev = { - x: this.game.x(trail[trail.length - 1]), - y: this.game.y(trail[trail.length - 1]), - }; - const line = new BezenhamLine(prev, cur); - let point = line.increment(); - while (point !== true) { - trail.push(this.game.ref(point.x, point.y)); - point = line.increment(); - } - newTrailSize = line.size(); - } else { - trail.push(unit.lastTile()); - } - - this.drawTrail( - trail.slice(-newTrailSize), - unit.owner().territoryColor(), - rel, - ); - this.drawSprite(unit); - if (!unit.isActive()) { - this.pendingTrailClears.push(unit); - } - } - - private handleMIRVWarhead(unit: UnitView) { - const rel = this.relationship(unit); - - this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile())); - - if (unit.isActive()) { - // Paint area - this.paintCell( - this.game.x(unit.tile()), - this.game.y(unit.tile()), - rel, - unit.owner().borderColor(), - 255, - ); - } - } - - private handleTradeShipEvent(unit: UnitView) { - this.drawSprite(unit); - } - - private handleTrainEvent(unit: UnitView) { - this.drawSprite(unit); - } - - private handleBoatEvent(unit: UnitView) { - const rel = this.relationship(unit); - - if (!this.unitToTrail.has(unit)) { - this.unitToTrail.set(unit, []); - } - const trail = this.unitToTrail.get(unit) ?? []; - trail.push(unit.lastTile()); - - // Paint trail - this.drawTrail(trail.slice(-1), unit.owner().territoryColor(), rel); - this.drawSprite(unit); - - if (!unit.isActive()) { - this.pendingTrailClears.push(unit); - } - } - - paintCell( - x: number, - y: number, - relationship: Relationship, - color: Colord, - alpha: number, - context: CanvasRenderingContext2D = this.context, - ) { - this.clearCell(x, y, context); - if (this.alternateView) { - switch (relationship) { - case Relationship.Self: - context.fillStyle = this.theme.selfColor().toRgbString(); - break; - case Relationship.Ally: - context.fillStyle = this.theme.allyColor().toRgbString(); - break; - case Relationship.Enemy: - context.fillStyle = this.theme.enemyColor().toRgbString(); - break; - } - } else { - context.fillStyle = color.alpha(alpha / 255).toRgbString(); - } - context.fillRect(x, y, 1, 1); - } - - clearCell( - x: number, - y: number, - context: CanvasRenderingContext2D = this.context, - ) { - context.clearRect(x, y, 1, 1); - } - - drawSprite(unit: UnitView, customTerritoryColor?: Colord) { - const x = this.game.x(unit.tile()); - const y = this.game.y(unit.tile()); - - let alternateViewColor: Colord | null = null; - - if (this.alternateView) { - let rel = this.relationship(unit); - const dstPortId = unit.targetUnitId(); - if (unit.type() === UnitType.TradeShip && dstPortId !== undefined) { - const target = this.game.unit(dstPortId)?.owner(); - const myPlayer = this.game.myPlayer(); - if (myPlayer !== null && target !== undefined) { - if (myPlayer === target) { - rel = Relationship.Self; - } else if (myPlayer.isFriendly(target)) { - rel = Relationship.Ally; - } - } - } - switch (rel) { - case Relationship.Self: - alternateViewColor = this.theme.selfColor(); - break; - case Relationship.Ally: - alternateViewColor = this.theme.allyColor(); - break; - case Relationship.Enemy: - alternateViewColor = this.theme.enemyColor(); - break; - } - } - - const sprite = getColoredSprite( - unit, - this.theme, - alternateViewColor ?? customTerritoryColor, - alternateViewColor ?? undefined, - ); - - if (unit.isActive()) { - const targetable = unit.targetable(); - if (!targetable) { - this.context.save(); - this.context.globalAlpha = 0.5; - } - this.context.drawImage( - sprite, - Math.round(x - sprite.width / 2), - Math.round(y - sprite.height / 2), - sprite.width, - sprite.width, - ); - if (!targetable) { - this.context.restore(); - } - } - } -} diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index 44bc83a855..a84f17b6a6 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -215,6 +215,9 @@ export class UnitView { isLoaded(): boolean | undefined { return this.data.loaded; } + missileTimerQueue(): number[] { + return this.data.missileTimerQueue; + } } export class PlayerView { diff --git a/tests/client/graphics/UILayer.test.ts b/tests/client/graphics/UILayer.test.ts index ab1300ab13..7ada986990 100644 --- a/tests/client/graphics/UILayer.test.ts +++ b/tests/client/graphics/UILayer.test.ts @@ -1,6 +1,5 @@ import { UILayer } from "../../../src/client/graphics/layers/UILayer"; import { UnitSelectionEvent } from "../../../src/client/InputHandler"; -import { UnitView } from "../../../src/core/game/GameView"; describe("UILayer", () => { let game: any; @@ -51,108 +50,4 @@ describe("UILayer", () => { ui["onUnitSelection"](event as UnitSelectionEvent); expect(ui.drawSelectionBox).toHaveBeenCalledWith(unit); }); - - it("should add and clear health bars", () => { - const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); - const unit = { - id: () => 1, - type: () => "Warship", - health: () => 5, - tile: () => ({}), - owner: () => ({}), - isActive: () => true, - createdAt: () => 1, - } as unknown as UnitView; - ui.drawHealthBar(unit); - expect(ui["allHealthBars"].has(1)).toBe(true); - - // a full hp unit doesn't have a health bar - unit.health = () => 10; - ui.drawHealthBar(unit); - expect(ui["allHealthBars"].has(1)).toBe(false); - - // a dead unit doesn't have a health bar - unit.health = () => 5; - ui.drawHealthBar(unit); - expect(ui["allHealthBars"].has(1)).toBe(true); - unit.health = () => 0; - ui.drawHealthBar(unit); - expect(ui["allHealthBars"].has(1)).toBe(false); - }); - - it("should remove health bars for inactive units", () => { - const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); - const unit = { - id: () => 1, - type: () => "Warship", - health: () => 5, - tile: () => ({}), - owner: () => ({}), - isActive: () => true, - } as unknown as UnitView; - ui.drawHealthBar(unit); - expect(ui["allHealthBars"].has(1)).toBe(true); - - // an inactive unit doesn't have a health bar - unit.isActive = () => false; - ui.drawHealthBar(unit); - expect(ui["allHealthBars"].has(1)).toBe(false); - }); - - it("should add loading bar for unit", () => { - const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); - const unit = { - id: () => 2, - tile: () => ({}), - isActive: () => true, - } as unknown as UnitView; - ui.createLoadingBar(unit); - expect(ui["allProgressBars"].has(2)).toBe(true); - }); - - it("should remove loading bar for inactive unit", () => { - const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); - const unit = { - id: () => 2, - type: () => "City", - isUnderConstruction: () => true, - owner: () => ({ id: () => 1 }), - tile: () => ({}), - isActive: () => true, - } as unknown as UnitView; - ui.onUnitEvent(unit); - expect(ui["allProgressBars"].has(2)).toBe(true); - - // an inactive unit should not have a loading bar - unit.isActive = () => false; - ui.tick(); - expect(ui["allProgressBars"].has(2)).toBe(false); - }); - - it("should remove loading bar for a finished progress bar", () => { - const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); - const unit = { - id: () => 2, - type: () => "City", - isUnderConstruction: () => true, - owner: () => ({ id: () => 1 }), - tile: () => ({}), - isActive: () => true, - createdAt: () => 1, - markedForDeletion: () => false, - } as unknown as UnitView; - ui.onUnitEvent(unit); - expect(ui["allProgressBars"].has(2)).toBe(true); - - game.ticks = () => 6; // simulate enough ticks for completion - // simulate construction finished - (unit as any).isUnderConstruction = () => false; - ui.tick(); - expect(ui["allProgressBars"].has(2)).toBe(false); - }); }); From 5b663fae148f90c9bb0337e403467244d49e068d Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 13:27:31 -0700 Subject: [PATCH 03/34] refactor: share renderer state shapes between game and WebGL renderer PlayerView/UnitView now wrap renderer-shaped state objects (PlayerState, PlayerStatic, UnitState) directly instead of holding engine wire types. GameView owns a long-lived FrameData object kept in sync each tick: players/units/tiles/trail/railroad are mutated in place; derived buffers (playerStatus, relationMatrix, allianceClusters, nukeTelegraphs, attackRings) and events are recomputed in a final populateFrame() pass. The renderer reads gameView.frameData() and the same byte-identical state objects PlayerView/UnitView wrap. WebGLFrameBuilder shrinks from ~270 to ~70 LOC: palette management + a single uploadFrameData() call, no per-frame UnitState allocation on the hot path. Wiring: maxPlayers=1024 on RendererConfig (pre-sizes NamePass/palette/ relation matrix textures); NamePass disabled so HTML NameLayer remains the only on-screen player names. Also: 39 new tests covering PlayerView/GameView/FrameData behavior; replace .data field access in three layer call sites with accessor methods (betrayals(), type(), getTraitorRemainingTicks()). --- src/client/ClientGameRunner.ts | 15 +- src/client/WebGLFrameBuilder.ts | 212 +-- src/client/graphics/layers/ChatModal.ts | 4 +- src/client/graphics/layers/PlayerPanel.ts | 4 +- src/client/view/GameView.ts | 1085 +++++++++++++ src/client/view/PlayerView.ts | 576 +++++++ src/client/view/UnitView.ts | 280 ++++ src/core/game/GameImpl.ts | 3 + src/core/game/GameMap.ts | 18 + src/core/game/GameView.ts | 1429 +---------------- tests/client/view/GameView.test.ts | 474 ++++++ tests/client/view/PlayerView.test.ts | 285 ++++ tests/client/view/UnitView.test.ts | 258 +++ .../core/game/GameMap.tileStateBuffer.test.ts | 30 + tests/util/viewStubs.ts | 224 +++ 15 files changed, 3278 insertions(+), 1619 deletions(-) create mode 100644 src/client/view/GameView.ts create mode 100644 src/client/view/PlayerView.ts create mode 100644 src/client/view/UnitView.ts create mode 100644 tests/client/view/GameView.test.ts create mode 100644 tests/client/view/PlayerView.test.ts create mode 100644 tests/client/view/UnitView.test.ts create mode 100644 tests/core/game/GameMap.tileStateBuffer.test.ts create mode 100644 tests/util/viewStubs.ts diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 22bec1aff6..ea734b2501 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -230,7 +230,6 @@ export function joinLobby( function mountWebGLDebugRenderer( terrainMap: TerrainMapData, - gameView: GameView, transformHandler: import("./graphics/TransformHandler").TransformHandler, ): { builder: WebGLFrameBuilder; syncCamera: () => void } { const gameMap = terrainMap.gameMap; @@ -257,11 +256,20 @@ function mountWebGLDebugRenderer( mapHeight, unitTypes: [...ALL_UNIT_TYPES], players: [], + // Pre-allocate renderer textures for up to 1024 players. We add players + // dynamically via view.addPlayers() as they come in from the simulation, + // but the NamePass / palette / relation matrix all need a static upper + // bound at construction time. + maxPlayers: 1024, }, terrainBytes, palette, ); + // Names are rendered by the existing HTML NameLayer; disable the renderer's + // NamePass to avoid drawing them twice. + view.getSettings().passEnabled.name = false; + window.addEventListener("keydown", (e) => { if (e.key === "\\") { glCanvas.style.display = @@ -287,7 +295,7 @@ function mountWebGLDebugRenderer( (window as unknown as { __webglView?: unknown }).__webglView = view; - return { builder: new WebGLFrameBuilder(view, gameView), syncCamera }; + return { builder: new WebGLFrameBuilder(view), syncCamera }; } async function createClientGame( @@ -343,7 +351,6 @@ async function createClientGame( const { builder: webglBuilder, syncCamera } = mountWebGLDebugRenderer( gameMap, - gameView, gameRenderer.transformHandler, ); gameRenderer.onPreRender = syncCamera; @@ -507,7 +514,7 @@ export class ClientGameRunner { this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash)); }); this.gameView.update(gu); - this.webglBuilder?.update(this.gameView, gu); + this.webglBuilder?.update(this.gameView); this.renderer.tick(); // Emit tick metrics event for performance overlay diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index be5a768199..5393b9279f 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -1,135 +1,32 @@ import { Colord } from "colord"; -import { PlayerType, TrainType, UnitType } from "../core/game/Game"; -import { GameUpdateType, GameUpdateViewData } from "../core/game/GameUpdates"; import { GameView } from "../core/game/GameView"; -import { RailroadCache } from "./render/frame/railroad-cache"; -import { TrailManager } from "./render/frame/trail-manager"; -import { - PlayerStatic, - UnitState, - GameView as WebGLGameView, -} from "./render/gl"; -import { - BonusEvent, - ConquestFx, - DeadUnitFx, - PlayerTypeEnum, - TrainType as RendererTrainType, -} from "./render/types"; - -const TRAIL_TYPES: ReadonlySet = new Set([ - UnitType.TransportShip, - UnitType.AtomBomb, - UnitType.HydrogenBomb, - UnitType.MIRV, - UnitType.MIRVWarhead, -]); +import { uploadFrameData } from "./render/frame/upload"; +import { PlayerStatic, GameView as WebGLGameView } from "./render/gl"; const PALETTE_SIZE = 4096; +/** + * The renderer-side glue between GameView (which already builds the full + * FrameData each tick) and the WebGL view. Two responsibilities: + * + * 1. Palette management — translate PlayerView colors into a Float32Array + * the renderer uploads to a 1D texture, and call view.addPlayers() when + * new players appear (this is a renderer-side lifecycle event, not part + * of FrameData). + * 2. Per-tick upload — pass the FrameData to the renderer's uploadFrameData + * helper, which dispatches to all the view.update*() methods. + */ export class WebGLFrameBuilder { - private readonly mapW: number; - private readonly mapH: number; - private readonly tileState: Uint16Array; private readonly palette: Float32Array; private readonly knownSmallIDs = new Set(); - private readonly railroadCache: RailroadCache; - private readonly trailManager: TrailManager; - private readonly unitMap = new Map(); - private readonly trailIds: number[] = []; - constructor( - private readonly view: WebGLGameView, - gameView: GameView, - ) { - this.mapW = gameView.width(); - this.mapH = gameView.height(); - this.tileState = new Uint16Array(this.mapW * this.mapH); + constructor(private readonly view: WebGLGameView) { this.palette = new Float32Array(PALETTE_SIZE * 2 * 4); - this.railroadCache = new RailroadCache(this.mapW, this.mapH); - this.trailManager = new TrailManager(this.mapW, this.mapH); } - update(gameView: GameView, gu: GameUpdateViewData): void { + update(gameView: GameView): void { this.syncPlayers(gameView); - this.fillTileState(gameView); - this.fillUnitMap(gameView); - this.trailManager.update(this.unitMap, this.trailIds); - this.view.uploadTileAndTrailState( - this.tileState, - this.trailManager.getTrailState(), - ); - this.trailManager.clearDirtyRows(); - this.applyRailroads(gu); - this.view.updateStructures(this.unitMap); - this.view.updateUnits(this.unitMap, gameView.ticks()); - this.applyFxEvents(gameView, gu); - } - - private applyFxEvents(gameView: GameView, gu: GameUpdateViewData): void { - const deadUnits: DeadUnitFx[] = []; - for (const u of gu.updates[GameUpdateType.Unit] ?? []) { - if (u.isActive) continue; - deadUnits.push({ - unitType: u.unitType, - pos: u.pos, - reachedTarget: u.reachedTarget, - }); - } - if (deadUnits.length > 0) { - this.view.applyDeadUnits(deadUnits); - } - - const conquests: ConquestFx[] = []; - for (const c of gu.updates[GameUpdateType.ConquestEvent] ?? []) { - const conquered = gameView.player(c.conqueredId); - const loc = conquered.nameLocation(); - conquests.push({ - x: loc.x, - y: loc.y, - gold: Number(c.gold), - }); - } - if (conquests.length > 0) { - this.view.applyConquestEvents(conquests); - } - - const bonuses: BonusEvent[] = []; - for (const b of gu.updates[GameUpdateType.BonusEvent] ?? []) { - const player = gameView.player(b.player); - bonuses.push({ - playerID: b.player, - smallID: player.smallID(), - tile: b.tile, - gold: Number(b.gold), - troops: b.troops, - }); - } - if (bonuses.length > 0) { - this.view.applyBonusEvents(bonuses); - } - } - - private fillUnitMap(gameView: GameView): void { - this.unitMap.clear(); - this.trailIds.length = 0; - for (const u of gameView.units()) { - this.unitMap.set(u.id(), toUnitState(u)); - if (TRAIL_TYPES.has(u.type())) { - this.trailIds.push(u.id()); - } - } - } - - private applyRailroads(gu: GameUpdateViewData): void { - this.railroadCache.apply(gu); - if (this.railroadCache.railroadDirty) { - this.view.uploadRailroadState(this.railroadCache.railroadState); - this.railroadCache.clearDirty(); - } - if (this.railroadCache.revealedRailTiles.length > 0) { - this.view.applyRailroadDust(this.railroadCache.revealedRailTiles); - } + uploadFrameData(this.view, gameView.frameData()); } private syncPlayers(gameView: GameView): void { @@ -142,14 +39,8 @@ export class WebGLFrameBuilder { this.writePaletteEntry(smallID, p.territoryColor(), p.borderColor()); newPlayers.push({ - smallID, - id: p.id(), - name: p.name(), - displayName: p.displayName(), - clientID: p.clientID(), - playerType: gamePlayerTypeToEnum(p.type()), - team: p.team() ?? null, - isLobbyCreator: p.isLobbyCreator(), + ...p.static, + flag: p.cosmetics.flag, color: p.territoryColor().toHex(), }); } @@ -177,71 +68,4 @@ export class WebGLFrameBuilder { this.palette[borderOff + 2] = borderRgba.b / 255; this.palette[borderOff + 3] = 1.0; } - - private fillTileState(gameView: GameView): void { - const w = this.mapW; - const h = this.mapH; - const buf = this.tileState; - for (let y = 0; y < h; y++) { - for (let x = 0; x < w; x++) { - const ref = gameView.ref(x, y); - let v = gameView.ownerID(ref) & 0x0fff; - if (gameView.hasFallout(ref)) v |= 1 << 13; - buf[y * w + x] = v; - } - } - } -} - -function toUnitState(u: import("../core/game/GameView").UnitView): UnitState { - return { - id: u.id(), - unitType: u.type(), - ownerID: u.owner().smallID(), - lastOwnerID: null, - pos: u.tile(), - lastPos: u.lastTile(), - isActive: u.isActive(), - reachedTarget: u.reachedTarget(), - retreating: false, - targetable: u.targetable(), - markedForDeletion: u.markedForDeletion(), - health: u.hasHealth() ? u.health() : null, - underConstruction: u.isUnderConstruction(), - targetUnitId: u.targetUnitId() ?? null, - targetTile: u.targetTile() ?? null, - troops: u.troops(), - missileTimerQueue: u.missileTimerQueue(), - level: u.level(), - hasTrainStation: u.hasTrainStation(), - trainType: trainTypeToNum(u.trainType()), - loaded: u.isLoaded() ?? null, - constructionStartTick: u.isUnderConstruction() ? u.createdAt() : null, - }; -} - -function trainTypeToNum(t: TrainType | undefined): number | null { - switch (t) { - case TrainType.Engine: - return RendererTrainType.Engine; - case TrainType.TailEngine: - return RendererTrainType.TailEngine; - case TrainType.Carriage: - return RendererTrainType.Carriage; - default: - return null; - } -} - -function gamePlayerTypeToEnum(t: PlayerType): PlayerTypeEnum { - switch (t) { - case PlayerType.Human: - return PlayerTypeEnum.Human; - case PlayerType.Bot: - return PlayerTypeEnum.Bot; - case PlayerType.Nation: - return PlayerTypeEnum.Nation; - default: - return PlayerTypeEnum.Bot; - } } diff --git a/src/client/graphics/layers/ChatModal.ts b/src/client/graphics/layers/ChatModal.ts index 23c0bbd4b0..0237b17ac9 100644 --- a/src/client/graphics/layers/ChatModal.ts +++ b/src/client/graphics/layers/ChatModal.ts @@ -277,7 +277,7 @@ export class ChatModal extends LitElement { console.log("Sent message:", sender); this.players = this.g .players() - .filter((p) => p.isAlive() && p.data.playerType !== PlayerType.Bot); + .filter((p) => p.isAlive() && p.type() !== PlayerType.Bot); this.recipient = recipient; this.sender = sender; @@ -311,7 +311,7 @@ export class ChatModal extends LitElement { if (sender && recipient) { this.players = this.g .players() - .filter((p) => p.isAlive() && p.data.playerType !== PlayerType.Bot); + .filter((p) => p.isAlive() && p.type() !== PlayerType.Bot); this.recipient = recipient; this.sender = sender; diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index 6a60675d20..abd86b74b2 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -410,7 +410,7 @@ export class PlayerPanel extends LitElement implements Layer { } private getTraitorRemainingSeconds(player: PlayerView): number | null { - const ticksLeft = player.data.traitorRemainingTicks ?? 0; + const ticksLeft = player.getTraitorRemainingTicks(); if (!player.isTraitor() || ticksLeft <= 0) return null; return Math.ceil(ticksLeft / 10); // 10 ticks = 1 second } @@ -608,7 +608,7 @@ export class PlayerPanel extends LitElement implements Layer { ${translateText("player_panel.betrayals")}

    - ${other.data.betrayals ?? 0} + ${other.betrayals()}
    diff --git a/src/client/view/GameView.ts b/src/client/view/GameView.ts new file mode 100644 index 0000000000..0e70bb85cf --- /dev/null +++ b/src/client/view/GameView.ts @@ -0,0 +1,1085 @@ +import { Config } from "../../core/configuration/Config"; +import { + Cell, + GameUpdates, + PlayerID, + TerrainType, + TerraNullius, + Tick, + Unit, + UnitInfo, + UnitType, +} from "../../core/game/Game"; +import { GameMap, TileRef } from "../../core/game/GameMap"; +import { + GameUpdateType, + GameUpdateViewData, + SpawnPhaseEndUpdate, +} from "../../core/game/GameUpdates"; +import { + MotionPlanRecord, + unpackMotionPlans, +} from "../../core/game/MotionPlans"; +import { TerrainMapData } from "../../core/game/TerrainMapLoader"; +import { TerraNulliusImpl } from "../../core/game/TerraNulliusImpl"; +import { UnitGrid, UnitPredicate } from "../../core/game/UnitGrid"; +import { ClientID, GameID, Player, PlayerCosmetics } from "../../core/Schemas"; +import { formatPlayerDisplayName } from "../../core/Util"; +import { WorkerClient } from "../../core/worker/WorkerClient"; +import { computeAllianceClusters } from "../render/frame/derive/alliance-clusters"; +import { extractAttackRings } from "../render/frame/derive/attack-rings"; +import { extractNukeTelegraphs } from "../render/frame/derive/nuke-telegraphs"; +import { computePlayerStatus } from "../render/frame/derive/player-status"; +import { buildRelationMatrix } from "../render/frame/derive/relation-matrix"; +import { RailroadCache } from "../render/frame/railroad-cache"; +import { TrailManager } from "../render/frame/trail-manager"; +import type { FrameData, NameEntry, TilePair } from "../render/types"; +import { STRUCTURE_TYPES } from "../render/types"; +import { PlayerView } from "./PlayerView"; +import { UnitView } from "./UnitView"; + +const TRAIL_TYPES: ReadonlySet = new Set([ + UnitType.TransportShip, + UnitType.AtomBomb, + UnitType.HydrogenBomb, + UnitType.MIRV, + UnitType.MIRVWarhead, +]); + +type TrainPlanState = { + planId: number; + startTick: number; + speed: number; + spacing: number; + carUnitIds: Uint32Array; + path: Uint32Array; + cursor: number; + usedTilesBuf: Uint32Array; + usedHead: number; + usedLen: number; + lastAdvancedTick: Tick; +}; + +export class GameView implements GameMap { + private lastUpdate: GameUpdateViewData | null; + private startTick: Tick | null = null; + private smallIDToID = new Map(); + private _players = new Map(); + private _units = new Map(); + /** + * Long-lived state maps (renderer's plain-object shape). Each entry shares + * its identity with the corresponding PlayerView.state / UnitView.state, so + * mutations through either path are visible everywhere. + */ + private _playerStates = new Map< + number, + import("../render/types").PlayerState + >(); + private _unitStates = new Map(); + private updatedTiles: TileRef[] = []; + private updatedTerrainTiles: TileRef[] = []; + + // ── FrameData accumulators (renderer-bound state) ───────────────────── + private trailManager!: TrailManager; + private railroadCache!: RailroadCache; + /** Long-lived NameEntry map for the renderer's NamePass. */ + private _names = new Map(); + /** Reusable scratch buffers for per-tick deltas. */ + private readonly _changedTilesScratch: TilePair[] = []; + private readonly _trailIdsScratch: number[] = []; + /** + * The single long-lived FrameData object. Fields are mutated in place each + * tick by update(). Renderer reads this each frame via frameData(). + */ + private _frame: FrameData; + private _structuresDirty = false; + /** True until first populateFrame() — controls full-vs-delta tile upload. */ + private _firstPopulate = true; + + private _myPlayer: PlayerView | null = null; + + private unitGrid: UnitGrid; + private unitMotionPlans = new Map< + number, + { + planId: number; + startTick: number; + ticksPerStep: number; + path: Uint32Array; + } + >(); + private trainMotionPlans = new Map(); + private trainUnitToEngine = new Map(); + + private toDelete = new Set(); + + private _cosmetics: Map = new Map(); + + private _map: GameMap; + + constructor( + public worker: WorkerClient, + private _config: Config, + private _mapData: TerrainMapData, + private _myClientID: ClientID | undefined, + private _myUsername: string, + private _myClanTag: string | null, + private _gameID: GameID, + humans: Player[], + ) { + this._map = this._mapData.gameMap; + this.lastUpdate = null; + this.unitGrid = new UnitGrid(this._map); + this._cosmetics = new Map( + humans.map((h) => [h.clientID, h.cosmetics ?? {}]), + ); + for (const nation of this._mapData.nations) { + // Nations don't have client ids, so we use their name as the key instead. + this._cosmetics.set(nation.name, { + flag: nation.flag ? `/flags/${nation.flag}.svg` : undefined, + } satisfies PlayerCosmetics); + } + for (const extra of this._mapData.additionalNations) { + // Only set if not already provided by a manifest nation with the same name. + if (this._cosmetics.has(extra.name)) continue; + this._cosmetics.set(extra.name, { + flag: extra.flag ? `/flags/${extra.flag}.svg` : undefined, + } satisfies PlayerCosmetics); + } + + const mapW = this._map.width(); + const mapH = this._map.height(); + this.trailManager = new TrailManager(mapW, mapH); + this.railroadCache = new RailroadCache(mapW, mapH); + + // Long-lived FrameData. Most fields are mutable references to long-lived + // buffers (tileState, trailState, etc.); some (_changedTilesScratch, + // derived arrays) are reused each tick. Properties marked `readonly` on + // FrameData only prevent reassignment, not mutation through the reference. + // events: fresh arrays we own; cleared and repopulated each tick. (Don't + // spread EMPTY_FRAME_EVENTS — that would share the module-level arrays.) + this._frame = { + tick: 0, + inSpawnPhase: true, + tileState: this._map.tileStateBuffer(), + trailState: this.trailManager.getTrailState(), + railroadState: this.railroadCache.railroadState, + units: this._unitStates, + players: this._playerStates, + names: this._names, + events: { + deadUnits: [], + conquestEvents: [], + unitUpdates: [], + playerUpdates: [], + allianceFormed: [], + allianceBroken: [], + allianceExpired: [], + embargoEvents: [], + targetEvents: [], + bonusEvents: [], + nukeIncoming: [], + emojis: [], + displayMessages: [], + wins: [], + gamePaused: null, + }, + changedTiles: this._changedTilesScratch, + railroadDirty: false, + revealedRailTiles: this.railroadCache.revealedRailTiles, + trailDirtyRowMin: 0, + trailDirtyRowMax: -1, + // Derived data — populated each tick by populateFrame(). Empty defaults + // here so the type is satisfied before the first update(). + playerStatus: new Map(), + relationMatrix: new Uint8Array(0), + relationSize: 0, + allianceClusters: new Map(), + nukeTelegraphs: [], + attackRings: [], + structuresDirty: false, + tileMode: "live", + }; + } + + isOnEdgeOfMap(ref: TileRef): boolean { + return this._map.isOnEdgeOfMap(ref); + } + + public updatesSinceLastTick(): GameUpdates | null { + return this.lastUpdate?.updates ?? null; + } + + public motionPlans(): ReadonlyMap< + number, + { + planId: number; + startTick: number; + ticksPerStep: number; + path: Uint32Array; + } + > { + return this.unitMotionPlans; + } + + private motionPlannedUnitIdsCache: number[] = []; + private motionPlannedUnitIdsDirty = true; + + private markMotionPlannedUnitIdsDirty(): void { + this.motionPlannedUnitIdsDirty = true; + } + + private rebuildMotionPlannedUnitIdsCacheIfDirty(): void { + if (!this.motionPlannedUnitIdsDirty) { + return; + } + this.motionPlannedUnitIdsDirty = false; + + const out = this.motionPlannedUnitIdsCache; + out.length = 0; + + for (const unitId of this.unitMotionPlans.keys()) { + out.push(unitId); + } + for (const [engineId, plan] of this.trainMotionPlans) { + out.push(engineId); + for (let i = 0; i < plan.carUnitIds.length; i++) { + const id = plan.carUnitIds[i] >>> 0; + if (id !== 0) out.push(id); + } + } + } + + public motionPlannedUnitIds(): number[] { + this.rebuildMotionPlannedUnitIdsCacheIfDirty(); + return this.motionPlannedUnitIdsCache; + } + + public isCatchingUp(): boolean { + return (this.lastUpdate?.pendingTurns ?? 0) > 1; + } + + public update(gu: GameUpdateViewData) { + this.toDelete.forEach((id) => { + this._units.delete(id); + this._unitStates.delete(id); + }); + this.toDelete.clear(); + + this.lastUpdate = gu; + + this.updatedTiles = []; + this.updatedTerrainTiles = []; + const packed = this.lastUpdate.packedTileUpdates; + for (let i = 0; i + 1 < packed.length; i += 2) { + const tile = packed[i]; + const state = packed[i + 1]; + const terrainChanged = this.updateTile(tile, state); + this.updatedTiles.push(tile); + if (terrainChanged) { + this.updatedTerrainTiles.push(tile); + } + } + + if (gu.packedMotionPlans) { + const records = unpackMotionPlans(gu.packedMotionPlans); + this.applyMotionPlanRecords(records); + } + + if (gu.updates === null) { + throw new Error("lastUpdate.updates not initialized"); + } + + const spawnPhaseEndUpdate = gu.updates[GameUpdateType.SpawnPhaseEnd][0] as + | SpawnPhaseEndUpdate + | undefined; + if (spawnPhaseEndUpdate) { + this.startTick = spawnPhaseEndUpdate.startTick; + } + + const myDisplayName = formatPlayerDisplayName( + this._myUsername, + this._myClanTag, + ); + + // Pass 1: ensure every player exists with up-to-date PlayerState. We need + // all smallIDs registered before pass 2 can translate embargo PlayerIDs. + gu.updates[GameUpdateType.Player].forEach((pu) => { + // Replace the local player's name/displayName with their own stored values. + // This way the user does not know they are being censored. + if (pu.clientID === this._myClientID) { + pu.name = this._myUsername; + pu.displayName = myDisplayName; + } + + this.smallIDToID.set(pu.smallID, pu.id); + let player = this._players.get(pu.id); + if (player !== undefined) { + player.applyUpdate(pu); + const nextNameData = gu.playerNameViewData[pu.id]; + if (nextNameData !== undefined) { + player.nameData = nextNameData; + } + } else { + player = new PlayerView( + this, + pu, + gu.playerNameViewData[pu.id], + // First check human by clientID, then check nation by name. + this._cosmetics.get(pu.clientID ?? "") ?? + this._cosmetics.get(pu.name) ?? + {}, + ); + this._players.set(pu.id, player); + this._playerStates.set(pu.smallID, player.state); + } + }); + + // Pass 2: translate engine embargoes (Set) → renderer-format + // stringified smallIDs. We could do this only on changes, but embargo sets + // are typically small (<50 entries per player). Pass through all in case + // any pu in this tick referenced a player created in this same tick. + gu.updates[GameUpdateType.Player].forEach((pu) => { + const player = this._players.get(pu.id); + if (player === undefined) return; + const smallIDStrings: string[] = []; + for (const otherPlayerID of pu.embargoes) { + const otherPV = this._players.get(otherPlayerID); + if (otherPV !== undefined) { + smallIDStrings.push(String(otherPV.smallID())); + } + } + player.setEmbargoSmallIDs(smallIDStrings); + }); + + if (this._myClientID) { + this._myPlayer ??= this.playerByClientID(this._myClientID); + } + + for (const unit of this._units.values()) { + unit._wasUpdated = false; + unit.lastPos = unit.lastPos.slice(-1); + } + gu.updates[GameUpdateType.Unit].forEach((update) => { + let unit = this._units.get(update.id); + const isStructure = STRUCTURE_TYPES.has(update.unitType); + if (unit !== undefined) { + // Structure changes that affect rendering: level changed, became + // inactive, or finished construction (underConstruction → !underConstruction). + if ( + isStructure && + (unit.state.level !== update.level || + unit.state.isActive !== update.isActive || + (unit.state.underConstruction && + !(update.underConstruction ?? false))) + ) { + this._structuresDirty = true; + } + unit.update(update); + } else { + unit = new UnitView(this, update); + this._units.set(update.id, unit); + this._unitStates.set(update.id, unit.state); + this.unitGrid.addUnit(unit); + if (isStructure) this._structuresDirty = true; + } + if (!update.isActive) { + this.unitGrid.removeUnit(unit); + } else if (unit.tile() !== unit.lastTile()) { + this.unitGrid.updateUnitCell(unit); + } + if (!unit.isActive()) { + // Wait until next tick to delete the unit. + this.toDelete.add(unit.id()); + if (this.unitMotionPlans.delete(unit.id())) { + this.markMotionPlannedUnitIdsDirty(); + } + this.clearTrainPlanForUnit(unit.id()); + } + }); + + this.advanceMotionPlannedUnits(gu.tick); + this.rebuildMotionPlannedUnitIdsCacheIfDirty(); + + this.populateFrame(gu); + } + + // ── FrameData population ──────────────────────────────────────────────── + + /** + * Populate the long-lived FrameData from this tick's updates and current + * state. Runs at the end of update() once all engine-driven mutations are + * complete. Mutates _frame fields in place; never reassigns them. + */ + private populateFrame(gu: GameUpdateViewData): void { + // Reset trail dirty markers for this tick. The trailManager.update() pass + // below repaints rows and re-sets these as it goes. + this.trailManager.clearDirtyRows(); + + // Railroad events accumulate into the cache; revealedRailTiles is cleared + // at the start of apply(). + this.railroadCache.apply(gu); + + // Trail update: walk active trail-type units and stamp/decay. + this._trailIdsScratch.length = 0; + for (const u of this._units.values()) { + if (u.isActive() && TRAIL_TYPES.has(u.type())) { + this._trailIdsScratch.push(u.id()); + } + } + this.trailManager.update( + this._unitStates as Map, + this._trailIdsScratch, + ); + + // Changed-tile delta refs (zero-copy: state field unused in live mode). + this._changedTilesScratch.length = 0; + for (let i = 0; i < this.updatedTiles.length; i++) { + this._changedTilesScratch.push({ ref: this.updatedTiles[i], state: 0 }); + } + + // Names map — rebuilt every tick. Cheap (one entry per player, no big + // arrays). Entry order is irrelevant for the renderer. + this._names.clear(); + for (const p of this._players.values()) { + this._names.set(p.id(), { + playerID: p.id(), + x: p.nameData?.x ?? 0, + y: p.nameData?.y ?? 0, + size: p.nameData?.size ?? 0, + }); + } + + // FrameEvents — clear arrays, then re-populate from this tick's updates. + this.buildFrameEvents(gu); + + // Update FrameData fields. Derived data is computed once per tick and + // stored directly on _frame (no intermediate copy). The renderer's + // `readonly` modifier on FrameData is just an external API hint — + // not enforced at runtime; we cast off to assign here. + const f = this._frame as { + -readonly [K in keyof FrameData]: FrameData[K]; + }; + f.tick = gu.tick; + f.inSpawnPhase = this.startTick === null; + f.railroadDirty = this.railroadCache.railroadDirty; + f.trailDirtyRowMin = this.trailManager.dirtyRowMin; + f.trailDirtyRowMax = this.trailManager.dirtyRowMax; + f.playerStatus = computePlayerStatus(this._playerStates, this._unitStates); + const rel = buildRelationMatrix(this._playerStates); + f.relationMatrix = rel.matrix; + f.relationSize = rel.size; + f.allianceClusters = computeAllianceClusters(this._playerStates); + f.nukeTelegraphs = extractNukeTelegraphs( + this._unitStates, + this._map.width(), + ); + f.attackRings = extractAttackRings(this._unitStates, this._map.width()); + f.structuresDirty = this._structuresDirty; + + // First populate: signal "full upload required" by nulling changedTiles. + // uploadFrameData() treats null as "no delta info; do a full tile+trail + // upload" — needed because the renderer's GPU buffers are empty. + if (this._firstPopulate) { + f.changedTiles = null; + f.structuresDirty = true; // force initial structure upload + this._firstPopulate = false; + } else { + f.changedTiles = this._changedTilesScratch; + } + + // Reset transient flags for next tick. + this.railroadCache.clearDirty(); + this._structuresDirty = false; + } + + /** Clear and repopulate _frame.events arrays from this tick's gu.updates. */ + private buildFrameEvents(gu: GameUpdateViewData): void { + const ev = this._frame.events; + ev.deadUnits.length = 0; + ev.conquestEvents.length = 0; + ev.bonusEvents.length = 0; + + for (const u of gu.updates[GameUpdateType.Unit] ?? []) { + if (u.isActive) continue; + ev.deadUnits.push({ + unitType: u.unitType, + pos: u.pos, + reachedTarget: u.reachedTarget, + }); + } + for (const c of gu.updates[GameUpdateType.ConquestEvent] ?? []) { + const conquered = this._players.get(c.conqueredId); + if (conquered === undefined) continue; + const loc = conquered.nameLocation(); + ev.conquestEvents.push({ + x: loc.x, + y: loc.y, + gold: Number(c.gold), + }); + } + for (const b of gu.updates[GameUpdateType.BonusEvent] ?? []) { + const player = this._players.get(b.player); + if (player === undefined) continue; + ev.bonusEvents.push({ + playerID: b.player, + smallID: player.smallID(), + tile: b.tile, + gold: Number(b.gold), + troops: b.troops, + }); + } + } + + /** Public accessor: the renderer reads this and uploads to the GPU. */ + frameData(): FrameData { + return this._frame; + } + + private advanceMotionPlannedUnits(currentTick: Tick): void { + for (const [unitId, plan] of this.unitMotionPlans) { + const unit = this._units.get(unitId); + if (!unit || !unit.isActive()) { + if (this.unitMotionPlans.delete(unitId)) { + this.markMotionPlannedUnitIdsDirty(); + } + continue; + } + + const oldTile = unit.tile(); + const dt = currentTick - plan.startTick; + const stepIndex = + dt <= 0 ? 0 : Math.floor(dt / Math.max(1, plan.ticksPerStep)); + const lastIndex = plan.path.length - 1; + const idx = Math.max(0, Math.min(lastIndex, stepIndex)); + const newTile = plan.path[idx] as TileRef; + + if (newTile !== oldTile) { + unit.applyDerivedPosition(newTile); + this.unitGrid.updateUnitCell(unit); + continue; + } + + // Once a plan is past its final step, `newTile` remains clamped to the last path tile. + // Drop finished plans to avoid repeatedly marking static units as updated each tick. + if (dt > 0 && stepIndex >= lastIndex) { + if (this.unitMotionPlans.delete(unitId)) { + this.markMotionPlannedUnitIdsDirty(); + } + } + } + + this.advanceTrainMotionPlannedUnits(currentTick); + } + + private clearTrainPlanForUnit(unitId: number): void { + const engineId = + this.trainUnitToEngine.get(unitId) ?? + (this.trainMotionPlans.has(unitId) ? unitId : null); + if (engineId === null) { + return; + } + const plan = this.trainMotionPlans.get(engineId); + if (!plan) { + this.trainUnitToEngine.delete(unitId); + return; + } + if (this.trainMotionPlans.delete(engineId)) { + this.markMotionPlannedUnitIdsDirty(); + } + this.trainUnitToEngine.delete(engineId); + for (let i = 0; i < plan.carUnitIds.length; i++) { + const id = plan.carUnitIds[i] >>> 0; + if (id !== 0) this.trainUnitToEngine.delete(id); + } + } + + private advanceTrainMotionPlannedUnits(currentTick: Tick): void { + const staleEngineIds: number[] = []; + for (const [engineId, plan] of this.trainMotionPlans) { + const engine = this._units.get(engineId); + if (!engine || !engine.isActive()) { + staleEngineIds.push(engineId); + continue; + } + + const steps = currentTick - plan.lastAdvancedTick; + if (steps <= 0) { + continue; + } + + const path = plan.path; + const lastIndex = path.length - 1; + const cap = plan.usedTilesBuf.length; + + const pushUsed = (tile: TileRef) => { + if (cap === 0) return; + if (plan.usedLen < cap) { + const idx = (plan.usedHead + plan.usedLen) % cap; + plan.usedTilesBuf[idx] = tile >>> 0; + plan.usedLen++; + } else { + plan.usedTilesBuf[plan.usedHead] = tile >>> 0; + plan.usedHead = (plan.usedHead + 1) % cap; + plan.usedLen = cap; + } + }; + + const usedGet = (index: number): TileRef | null => { + if (index < 0 || index >= plan.usedLen || cap === 0) return null; + const idx = (plan.usedHead + index) % cap; + return plan.usedTilesBuf[idx] as TileRef; + }; + + let didMove = false; + for (let step = 0; step < steps; step++) { + const cursor = plan.cursor; + if (cursor >= lastIndex) { + break; + } + for (let i = 0; i < plan.speed && cursor + i < path.length; i++) { + pushUsed(path[cursor + i] as TileRef); + } + + plan.cursor = Math.min(lastIndex, cursor + plan.speed); + + for (let i = plan.carUnitIds.length - 1; i >= 0; --i) { + const carId = plan.carUnitIds[i] >>> 0; + if (carId === 0) continue; + const car = this._units.get(carId); + if (!car || !car.isActive()) { + continue; + } + const carTileIndex = (i + 1) * plan.spacing + 2; + const tile = usedGet(carTileIndex); + if (tile !== null) { + const oldTile = car.tile(); + if (tile !== oldTile) { + car.applyDerivedPosition(tile); + this.unitGrid.updateUnitCell(car); + didMove = true; + } + } + } + + const newEngineTile = path[plan.cursor] as TileRef; + const oldEngineTile = engine.tile(); + if (newEngineTile !== oldEngineTile) { + engine.applyDerivedPosition(newEngineTile); + this.unitGrid.updateUnitCell(engine); + didMove = true; + } + } + + plan.lastAdvancedTick = currentTick; + + // Preserve the final-step redraw (plan remains for the tick where motion ends), + // then clear once the train has settled and no longer moves. + // Note: trains are currently deleted at the end of TrainExecution, and the ensuing + // `Unit` update (isActive=false) also clears any associated motion plan records. + // This expiry is defensive to avoid keeping stale plans around if that behavior changes. + if (!didMove && plan.cursor >= lastIndex) { + staleEngineIds.push(engineId); + } + } + + for (const engineId of staleEngineIds) { + this.clearTrainPlanForUnit(engineId); + } + } + + private applyMotionPlanRecords(records: readonly MotionPlanRecord[]): void { + for (const record of records) { + switch (record.kind) { + case "grid": { + if (record.ticksPerStep < 1 || record.path.length < 1) { + break; + } + const existing = this.unitMotionPlans.get(record.unitId); + if (existing && record.planId <= existing.planId) { + break; + } + + const path = + record.path instanceof Uint32Array + ? record.path + : Uint32Array.from(record.path); + + this.unitMotionPlans.set(record.unitId, { + planId: record.planId, + startTick: record.startTick, + ticksPerStep: record.ticksPerStep, + path, + }); + this.markMotionPlannedUnitIdsDirty(); + break; + } + case "train": { + if (record.speed < 1 || record.path.length < 1) { + break; + } + const existing = this.trainMotionPlans.get(record.engineUnitId); + if (existing && record.planId <= existing.planId) { + break; + } + if (existing) { + this.clearTrainPlanForUnit(record.engineUnitId); + } + + const carUnitIds = + record.carUnitIds instanceof Uint32Array + ? record.carUnitIds + : Uint32Array.from(record.carUnitIds); + const path = + record.path instanceof Uint32Array + ? record.path + : Uint32Array.from(record.path); + + const usedCap = carUnitIds.length * record.spacing + 3; + const usedTilesBuf = new Uint32Array(Math.max(0, usedCap)); + + this.trainMotionPlans.set(record.engineUnitId, { + planId: record.planId, + startTick: record.startTick, + speed: record.speed, + spacing: record.spacing, + carUnitIds, + path, + cursor: 0, + usedTilesBuf, + usedHead: 0, + usedLen: 0, + lastAdvancedTick: record.startTick, + }); + this.markMotionPlannedUnitIdsDirty(); + + this.trainUnitToEngine.set(record.engineUnitId, record.engineUnitId); + for (let i = 0; i < carUnitIds.length; i++) { + const carId = carUnitIds[i] >>> 0; + if (carId !== 0) + this.trainUnitToEngine.set(carId, record.engineUnitId); + } + break; + } + } + } + } + + recentlyUpdatedTiles(): TileRef[] { + return this.updatedTiles; + } + + recentlyUpdatedTerrainTiles(): TileRef[] { + return this.updatedTerrainTiles; + } + + nearbyUnits( + tile: TileRef, + searchRange: number, + types: UnitType | readonly UnitType[], + predicate?: UnitPredicate, + ): Array<{ unit: UnitView; distSquared: number }> { + return this.unitGrid.nearbyUnits( + tile, + searchRange, + types, + predicate, + ) as Array<{ + unit: UnitView; + distSquared: number; + }>; + } + + hasUnitNearby( + tile: TileRef, + searchRange: number, + type: UnitType, + playerId?: PlayerID, + includeUnderConstruction?: boolean, + ) { + return this.unitGrid.hasUnitNearby( + tile, + searchRange, + type, + playerId, + includeUnderConstruction, + ); + } + + anyUnitNearby( + tile: TileRef, + searchRange: number, + types: readonly UnitType[], + predicate: (unit: UnitView) => boolean, + playerId?: PlayerID, + includeUnderConstruction?: boolean, + ): boolean { + return this.unitGrid.anyUnitNearby( + tile, + searchRange, + types, + predicate as (unit: Unit | UnitView) => boolean, + playerId, + includeUnderConstruction, + ); + } + + myClientID(): ClientID | undefined { + return this._myClientID; + } + + myPlayer(): PlayerView | null { + return this._myPlayer; + } + + player(id: PlayerID): PlayerView { + const player = this._players.get(id); + if (player === undefined) { + throw Error(`player id ${id} not found`); + } + return player; + } + + players(): PlayerView[] { + return Array.from(this._players.values()); + } + + playerBySmallID(id: number): PlayerView | TerraNullius { + if (id === 0) { + return new TerraNulliusImpl(); + } + const playerId = this.smallIDToID.get(id); + if (playerId === undefined) { + throw new Error(`small id ${id} not found`); + } + return this.player(playerId); + } + + playerByClientID(id: ClientID): PlayerView | null { + const player = + Array.from(this._players.values()).filter( + (p) => p.clientID() === id, + )[0] ?? null; + if (player === null) { + return null; + } + return player; + } + hasPlayer(id: PlayerID): boolean { + return false; + } + playerViews(): PlayerView[] { + return Array.from(this._players.values()); + } + + owner(tile: TileRef): PlayerView | TerraNullius { + return this.playerBySmallID(this.ownerID(tile)); + } + + ticks(): Tick { + if (this.lastUpdate === null) return 0; + return this.lastUpdate.tick; + } + inSpawnPhase(): boolean { + return this.startTick === null; + } + + isSpawnImmunityActive(): boolean { + return ( + this.inSpawnPhase() || + this.ticksSinceStart() < this._config.spawnImmunityDuration() + ); + } + isNationSpawnImmunityActive(): boolean { + return ( + this.inSpawnPhase() || + this.ticksSinceStart() < this._config.nationSpawnImmunityDuration() + ); + } + + elapsedGameSeconds(): number { + return this.ticksSinceStart() / 10; + } + + ticksSinceStart(): Tick { + if (this.inSpawnPhase()) { + return 0; + } + + return Math.max(0, this.ticks() - this.startTick!); + } + config(): Config { + return this._config; + } + units(...types: UnitType[]): UnitView[] { + if (types.length === 0) { + return Array.from(this._units.values()).filter((u) => u.isActive()); + } + return Array.from(this._units.values()).filter( + (u) => u.isActive() && types.includes(u.type()), + ); + } + unit(id: number): UnitView | undefined { + return this._units.get(id); + } + unitInfo(type: UnitType): UnitInfo { + return this._config.unitInfo(type); + } + + /** + * Long-lived map of UnitState records, keyed by unit ID. Mutated in place + * each tick by `update()`. Renderer code reads from this directly — the + * UnitView wrapping each entry shares the same UnitState reference. + * + * Includes inactive units; renderer filters by `state.isActive`. + */ + unitStates(): ReadonlyMap { + return this._unitStates; + } + + /** + * Long-lived map of PlayerState records, keyed by smallID. Mutated in place + * each tick by `update()`. Renderer code reads from this directly. + */ + playerStates(): ReadonlyMap { + return this._playerStates; + } + + ref(x: number, y: number): TileRef { + return this._map.ref(x, y); + } + isValidRef(ref: TileRef): boolean { + return this._map.isValidRef(ref); + } + x(ref: TileRef): number { + return this._map.x(ref); + } + y(ref: TileRef): number { + return this._map.y(ref); + } + cell(ref: TileRef): Cell { + return this._map.cell(ref); + } + width(): number { + return this._map.width(); + } + height(): number { + return this._map.height(); + } + numLandTiles(): number { + return this._map.numLandTiles(); + } + isValidCoord(x: number, y: number): boolean { + return this._map.isValidCoord(x, y); + } + isLand(ref: TileRef): boolean { + return this._map.isLand(ref); + } + isOceanShore(ref: TileRef): boolean { + return this._map.isOceanShore(ref); + } + isOcean(ref: TileRef): boolean { + return this._map.isOcean(ref); + } + isShoreline(ref: TileRef): boolean { + return this._map.isShoreline(ref); + } + magnitude(ref: TileRef): number { + return this._map.magnitude(ref); + } + terrainByte(ref: TileRef): number { + return this._map.terrainByte(ref); + } + setWater(ref: TileRef): void { + this._map.setWater(ref); + } + setShorelineBit(ref: TileRef): void { + this._map.setShorelineBit(ref); + } + clearShorelineBit(ref: TileRef): void { + this._map.clearShorelineBit(ref); + } + setOcean(ref: TileRef): void { + this._map.setOcean(ref); + } + setMagnitude(ref: TileRef, value: number): void { + this._map.setMagnitude(ref, value); + } + ownerID(ref: TileRef): number { + return this._map.ownerID(ref); + } + hasOwner(ref: TileRef): boolean { + return this._map.hasOwner(ref); + } + setOwnerID(ref: TileRef, playerId: number): void { + return this._map.setOwnerID(ref, playerId); + } + hasFallout(ref: TileRef): boolean { + return this._map.hasFallout(ref); + } + setFallout(ref: TileRef, value: boolean): void { + return this._map.setFallout(ref, value); + } + isBorder(ref: TileRef): boolean { + return this._map.isBorder(ref); + } + neighbors(ref: TileRef): TileRef[] { + return this._map.neighbors(ref); + } + isWater(ref: TileRef): boolean { + return this._map.isWater(ref); + } + isLake(ref: TileRef): boolean { + return this._map.isLake(ref); + } + isShore(ref: TileRef): boolean { + return this._map.isShore(ref); + } + cost(ref: TileRef): number { + return this._map.cost(ref); + } + terrainType(ref: TileRef): TerrainType { + return this._map.terrainType(ref); + } + forEachTile(fn: (tile: TileRef) => void): void { + return this._map.forEachTile(fn); + } + manhattanDist(c1: TileRef, c2: TileRef): number { + return this._map.manhattanDist(c1, c2); + } + euclideanDistSquared(c1: TileRef, c2: TileRef): number { + return this._map.euclideanDistSquared(c1, c2); + } + circleSearch( + tile: TileRef, + radius: number, + filter?: (tile: TileRef, d2: number) => boolean, + ): Set { + return this._map.circleSearch(tile, radius, filter); + } + bfs( + tile: TileRef, + filter: (gm: GameMap, tile: TileRef) => boolean, + ): Set { + return this._map.bfs(tile, filter); + } + tileState(tile: TileRef): number { + return this._map.tileState(tile); + } + tileStateBuffer(): Uint16Array { + return this._map.tileStateBuffer(); + } + updateTile(tile: TileRef, state: number): boolean { + return this._map.updateTile(tile, state); + } + numTilesWithFallout(): number { + return this._map.numTilesWithFallout(); + } + gameID(): GameID { + return this._gameID; + } + + focusedPlayer(): PlayerView | null { + return this.myPlayer(); + } +} diff --git a/src/client/view/PlayerView.ts b/src/client/view/PlayerView.ts new file mode 100644 index 0000000000..6c2272d99a --- /dev/null +++ b/src/client/view/PlayerView.ts @@ -0,0 +1,576 @@ +import { Colord, colord } from "colord"; +import { base64url } from "jose"; +import { ColorPalette } from "../../core/CosmeticSchemas"; +import { PatternDecoder } from "../../core/PatternDecoder"; +import { ClientID, PlayerCosmetics } from "../../core/Schemas"; +import { createRandomName } from "../../core/Util"; +import { + BuildableUnit, + Cell, + EmojiMessage, + Gold, + NameViewData, + PlayerActions, + PlayerBorderTiles, + PlayerBuildableUnitType, + PlayerID, + PlayerProfile, + PlayerType, + Team, + Tick, + UnitType, +} from "../../core/game/Game"; +import { TileRef } from "../../core/game/GameMap"; +import { + AllianceView, + AttackUpdate, + PlayerUpdate, +} from "../../core/game/GameUpdates"; +import { UserSettings } from "../../core/game/UserSettings"; +import { PlayerState, PlayerStatic, PlayerTypeEnum } from "../render/types"; +import { GameView } from "./GameView"; +import { UnitView } from "./UnitView"; + +const userSettings: UserSettings = new UserSettings(); + +const FRIENDLY_TINT_TARGET = { r: 0, g: 255, b: 0, a: 1 }; +const EMBARGO_TINT_TARGET = { r: 255, g: 0, b: 0, a: 1 }; +const BORDER_TINT_RATIO = 0.35; + +function gamePlayerTypeToEnum(t: PlayerType): PlayerTypeEnum { + switch (t) { + case PlayerType.Human: + return PlayerTypeEnum.Human; + case PlayerType.Bot: + return PlayerTypeEnum.Bot; + case PlayerType.Nation: + return PlayerTypeEnum.Nation; + default: + return PlayerTypeEnum.Bot; + } +} + +function staticFromUpdate(pu: PlayerUpdate): PlayerStatic { + return { + smallID: pu.smallID, + id: pu.id, + name: pu.name, + displayName: pu.displayName, + clientID: pu.clientID, + playerType: gamePlayerTypeToEnum(pu.playerType), + team: pu.team ?? null, + isLobbyCreator: pu.isLobbyCreator, + }; +} + +function stateFromUpdate(pu: PlayerUpdate): PlayerState { + // embargoes: Set on the wire, but the renderer expects + // stringified smallIDs. GameView fills these in via setEmbargoes() because + // it has the PlayerID → smallID lookup table. + return { + smallID: pu.smallID, + isAlive: pu.isAlive, + isDisconnected: pu.isDisconnected, + tilesOwned: pu.tilesOwned, + gold: Number(pu.gold), + troops: pu.troops, + isTraitor: pu.isTraitor, + traitorRemainingTicks: Math.max(0, pu.traitorRemainingTicks ?? 0), + betrayals: pu.betrayals, + hasSpawned: pu.hasSpawned, + lastDeleteUnitTick: pu.lastDeleteUnitTick, + allies: pu.allies.slice(), + embargoes: [], + targets: pu.targets.slice(), + outgoingAttacks: pu.outgoingAttacks, + incomingAttacks: pu.incomingAttacks, + outgoingAllianceRequests: pu.outgoingAllianceRequests.slice(), + alliances: pu.alliances, + outgoingEmojis: pu.outgoingEmojis, + }; +} + +function applyStateUpdate(target: PlayerState, pu: PlayerUpdate): void { + // smallID is identity — never changes for a given PlayerView. + target.isAlive = pu.isAlive; + target.isDisconnected = pu.isDisconnected; + target.tilesOwned = pu.tilesOwned; + target.gold = Number(pu.gold); + target.troops = pu.troops; + target.isTraitor = pu.isTraitor; + target.traitorRemainingTicks = Math.max(0, pu.traitorRemainingTicks ?? 0); + target.betrayals = pu.betrayals; + target.hasSpawned = pu.hasSpawned; + target.lastDeleteUnitTick = pu.lastDeleteUnitTick; + // Slice() to detach from the wire object — accumulated state mustn't share + // mutable arrays with per-tick update payloads. + target.allies = pu.allies.slice(); + target.targets = pu.targets.slice(); + target.outgoingAllianceRequests = pu.outgoingAllianceRequests.slice(); + target.outgoingAttacks = pu.outgoingAttacks; + target.incomingAttacks = pu.incomingAttacks; + target.alliances = pu.alliances; + target.outgoingEmojis = pu.outgoingEmojis; +} + +export class PlayerView { + public anonymousName: string | null = null; + private decoder?: PatternDecoder; + + /** Long-lived renderer state — mutated in place by applyUpdate(). */ + public state: PlayerState; + /** Static header data — set once at construction, never mutated. */ + public static: PlayerStatic; + + private _territoryColor: Colord; + private _borderColor: Colord; + // Update here to include structure light and dark colors + private _structureColors: { light: Colord; dark: Colord }; + + // Pre-computed border color variants + private _borderColorNeutral: Colord; + private _borderColorFriendly: Colord; + private _borderColorEmbargo: Colord; + private _borderColorDefendedNeutral: { light: Colord; dark: Colord }; + private _borderColorDefendedFriendly: { light: Colord; dark: Colord }; + private _borderColorDefendedEmbargo: { light: Colord; dark: Colord }; + + constructor( + private game: GameView, + data: PlayerUpdate, + public nameData: NameViewData, + public cosmetics: PlayerCosmetics, + ) { + this.state = stateFromUpdate(data); + this.static = staticFromUpdate(data); + + if (data.clientID === game.myClientID()) { + this.anonymousName = data.name; + } else { + this.anonymousName = createRandomName(data.name, data.playerType); + } + + const theme = this.game.config().theme(); + + const defaultTerritoryColor = theme.territoryColor(this); + const defaultBorderColor = theme.borderColor(defaultTerritoryColor); + + const pattern = userSettings.territoryPatterns() + ? this.cosmetics.pattern + : undefined; + if (pattern) { + pattern.colorPalette ??= { + name: "", + primaryColor: defaultTerritoryColor.toHex(), + secondaryColor: defaultBorderColor.toHex(), + } satisfies ColorPalette; + } + + if (this.team() === null) { + this._territoryColor = colord( + this.cosmetics.color?.color ?? + pattern?.colorPalette?.primaryColor ?? + defaultTerritoryColor.toHex(), + ); + } else { + this._territoryColor = defaultTerritoryColor; + } + + this._structureColors = theme.structureColors(this._territoryColor); + + const maybeFocusedBorderColor = + this.game.myClientID() === data.clientID + ? theme.focusedBorderColor() + : defaultBorderColor; + + this._borderColor = new Colord( + pattern?.colorPalette?.secondaryColor ?? + this.cosmetics.color?.color ?? + maybeFocusedBorderColor.toHex(), + ); + + const baseRgb = this._borderColor.toRgb(); + + this._borderColorNeutral = this._borderColor; + + this._borderColorFriendly = colord({ + r: Math.round( + baseRgb.r * (1 - BORDER_TINT_RATIO) + + FRIENDLY_TINT_TARGET.r * BORDER_TINT_RATIO, + ), + g: Math.round( + baseRgb.g * (1 - BORDER_TINT_RATIO) + + FRIENDLY_TINT_TARGET.g * BORDER_TINT_RATIO, + ), + b: Math.round( + baseRgb.b * (1 - BORDER_TINT_RATIO) + + FRIENDLY_TINT_TARGET.b * BORDER_TINT_RATIO, + ), + a: baseRgb.a, + }); + + this._borderColorEmbargo = colord({ + r: Math.round( + baseRgb.r * (1 - BORDER_TINT_RATIO) + + EMBARGO_TINT_TARGET.r * BORDER_TINT_RATIO, + ), + g: Math.round( + baseRgb.g * (1 - BORDER_TINT_RATIO) + + EMBARGO_TINT_TARGET.g * BORDER_TINT_RATIO, + ), + b: Math.round( + baseRgb.b * (1 - BORDER_TINT_RATIO) + + EMBARGO_TINT_TARGET.b * BORDER_TINT_RATIO, + ), + a: baseRgb.a, + }); + + this._borderColorDefendedNeutral = theme.defendedBorderColors( + this._borderColorNeutral, + ); + this._borderColorDefendedFriendly = theme.defendedBorderColors( + this._borderColorFriendly, + ); + this._borderColorDefendedEmbargo = theme.defendedBorderColors( + this._borderColorEmbargo, + ); + + this.decoder = + pattern === undefined + ? undefined + : new PatternDecoder(pattern, base64url.decode); + } + + /** + * Update mutable state in place. Called by GameView.update() each tick the + * player appears in the PlayerUpdate stream. + */ + applyUpdate(pu: PlayerUpdate): void { + applyStateUpdate(this.state, pu); + } + + /** Set the renderer-format embargoes (stringified smallIDs). */ + setEmbargoSmallIDs(smallIDStrings: string[]): void { + this.state.embargoes = smallIDStrings; + } + + territoryColor(tile?: TileRef): Colord { + if (tile === undefined || this.decoder === undefined) { + return this._territoryColor; + } + const isPrimary = this.decoder.isPrimary( + this.game.x(tile), + this.game.y(tile), + ); + return isPrimary ? this._territoryColor : this._borderColor; + } + + structureColors(): { light: Colord; dark: Colord } { + return this._structureColors; + } + + /** + * Border color for a tile: + * - Tints by neighbor relations (embargo → red, friendly → green, else neutral). + * - If defended, applies theme checkerboard to the tinted color. + */ + borderColor(tile?: TileRef, isDefended: boolean = false): Colord { + if (tile === undefined) { + return this._borderColor; + } + + const { hasEmbargo, hasFriendly } = this.borderRelationFlags(tile); + + let baseColor: Colord; + let defendedColors: { light: Colord; dark: Colord }; + + if (hasEmbargo) { + baseColor = this._borderColorEmbargo; + defendedColors = this._borderColorDefendedEmbargo; + } else if (hasFriendly) { + baseColor = this._borderColorFriendly; + defendedColors = this._borderColorDefendedFriendly; + } else { + baseColor = this._borderColorNeutral; + defendedColors = this._borderColorDefendedNeutral; + } + + if (!isDefended) { + return baseColor; + } + + const x = this.game.x(tile); + const y = this.game.y(tile); + const lightTile = + (x % 2 === 0 && y % 2 === 0) || (y % 2 === 1 && x % 2 === 1); + return lightTile ? defendedColors.light : defendedColors.dark; + } + + /** + * Border relation flags for a tile, used by both CPU and WebGL renderers. + */ + borderRelationFlags(tile: TileRef): { + hasEmbargo: boolean; + hasFriendly: boolean; + } { + const mySmallID = this.smallID(); + let hasEmbargo = false; + let hasFriendly = false; + + for (const n of this.game.neighbors(tile)) { + if (!this.game.hasOwner(n)) { + continue; + } + + const otherOwner = this.game.owner(n); + if (!otherOwner.isPlayer() || otherOwner.smallID() === mySmallID) { + continue; + } + + if (this.hasEmbargo(otherOwner)) { + hasEmbargo = true; + break; + } + + if (this.isFriendly(otherOwner) || otherOwner.isFriendly(this)) { + hasFriendly = true; + } + } + return { hasEmbargo, hasFriendly }; + } + + async actions( + tile?: TileRef, + units?: readonly PlayerBuildableUnitType[] | null, + ): Promise { + return this.game.worker.playerInteraction( + this.id(), + tile && this.game.x(tile), + tile && this.game.y(tile), + units, + ); + } + + async buildables( + tile?: TileRef, + units?: readonly PlayerBuildableUnitType[], + ): Promise { + return this.game.worker.playerBuildables( + this.id(), + tile && this.game.x(tile), + tile && this.game.y(tile), + units, + ); + } + + async borderTiles(): Promise { + return this.game.worker.playerBorderTiles(this.id()); + } + + outgoingAttacks(): AttackUpdate[] { + return this.state.outgoingAttacks; + } + + incomingAttacks(): AttackUpdate[] { + return this.state.incomingAttacks; + } + + async attackClusteredPositions( + attackID?: string, + ): Promise<{ id: string; positions: Cell[] }[]> { + return this.game.worker.attackClusteredPositions(this.smallID(), attackID); + } + + units(...types: UnitType[]): UnitView[] { + return this.game + .units(...types) + .filter((u) => u.owner().smallID() === this.smallID()); + } + + nameLocation(): NameViewData { + return this.nameData; + } + + smallID(): number { + return this.state.smallID; + } + + name(): string { + return this.anonymousName !== null && userSettings.anonymousNames() + ? this.anonymousName + : this.static.name; + } + displayName(): string { + return this.anonymousName !== null && userSettings.anonymousNames() + ? this.anonymousName + : this.static.displayName; + } + + clientID(): ClientID | null { + return this.static.clientID; + } + id(): PlayerID { + return this.static.id; + } + team(): Team | null { + return this.static.team; + } + type(): PlayerType { + // Map PlayerStatic's numeric enum back to engine string enum. + switch (this.static.playerType) { + case PlayerTypeEnum.Human: + return PlayerType.Human; + case PlayerTypeEnum.Bot: + return PlayerType.Bot; + case PlayerTypeEnum.Nation: + return PlayerType.Nation; + default: + return PlayerType.Bot; + } + } + isAlive(): boolean { + return this.state.isAlive; + } + isPlayer(): this is PlayerView { + return true; + } + numTilesOwned(): number { + return this.state.tilesOwned; + } + allies(): PlayerView[] { + return this.state.allies.map( + (a) => this.game.playerBySmallID(a) as PlayerView, + ); + } + targets(): PlayerView[] { + return this.state.targets.map( + (id) => this.game.playerBySmallID(id) as PlayerView, + ); + } + gold(): Gold { + // Engine Gold is bigint; renderer state stores number. Convert back at the + // accessor for game-code that still expects bigint semantics. + return BigInt(this.state.gold); + } + + troops(): number { + return this.state.troops; + } + + totalUnitLevels(type: UnitType): number { + return this.units(type) + .filter((unit) => !unit.isUnderConstruction()) + .map((unit) => unit.level()) + .reduce((a, b) => a + b, 0); + } + + isMe(): boolean { + return this.smallID() === this.game.myPlayer()?.smallID(); + } + + isLobbyCreator(): boolean { + return this.static.isLobbyCreator; + } + + isAlliedWith(other: PlayerView): boolean { + return this.state.allies.some((n) => other.smallID() === n); + } + + isOnSameTeam(other: PlayerView): boolean { + return this.static.team !== null && this.static.team === other.static.team; + } + + isFriendly(other: PlayerView): boolean { + return this.isAlliedWith(other) || this.isOnSameTeam(other); + } + + isRequestingAllianceWith(other: PlayerView) { + return this.state.outgoingAllianceRequests.some((id) => other.id() === id); + } + + alliances(): AllianceView[] { + return this.state.alliances; + } + + hasEmbargoAgainst(other: PlayerView): boolean { + const otherSmallIDStr = String(other.smallID()); + return this.state.embargoes.includes(otherSmallIDStr); + } + + hasEmbargo(other: PlayerView): boolean { + return this.hasEmbargoAgainst(other) || other.hasEmbargoAgainst(this); + } + + profile(): Promise { + return this.game.worker.playerProfile(this.smallID()); + } + + bestTransportShipSpawn(targetTile: TileRef): Promise { + return this.game.worker.transportShipSpawn(this.id(), targetTile); + } + + transitiveTargets(): PlayerView[] { + const result: PlayerView[] = []; + + // Add own targets + for (const id of this.state.targets) { + result.push(this.game.playerBySmallID(id) as PlayerView); + } + + // Add allies' targets + for (const allyID of this.state.allies) { + const ally = this.game.playerBySmallID(allyID) as PlayerView; + for (const targetId of ally.state.targets) { + result.push(this.game.playerBySmallID(targetId) as PlayerView); + } + } + + // Add teammates' targets + const myTeam = this.static.team; + if (myTeam !== null) { + for (const p of this.game.playerViews()) { + if (p !== this && p.static.team === myTeam) { + for (const targetId of p.state.targets) { + result.push(this.game.playerBySmallID(targetId) as PlayerView); + } + } + } + } + + return result; + } + + isTraitor(): boolean { + return this.state.isTraitor; + } + getTraitorRemainingTicks(): number { + return this.state.traitorRemainingTicks; + } + betrayals(): number { + return this.state.betrayals; + } + outgoingEmojis(): EmojiMessage[] { + return this.state.outgoingEmojis; + } + + hasSpawned(): boolean { + return this.state.hasSpawned; + } + isDisconnected(): boolean { + return this.state.isDisconnected; + } + + lastDeleteUnitTick(): Tick { + return this.state.lastDeleteUnitTick; + } + + deleteUnitCooldown(): number { + return ( + Math.max( + 0, + this.game.config().deleteUnitCooldown() - + (this.game.ticks() + 1 - this.lastDeleteUnitTick()), + ) / 10 + ); + } +} diff --git a/src/client/view/UnitView.ts b/src/client/view/UnitView.ts new file mode 100644 index 0000000000..46b2c24b03 --- /dev/null +++ b/src/client/view/UnitView.ts @@ -0,0 +1,280 @@ +import { + Tick, + TrainType, + TransportShipState, + UnitType, + WarshipState, +} from "../../core/game/Game"; +import { TileRef } from "../../core/game/GameMap"; +import { UnitUpdate } from "../../core/game/GameUpdates"; +import type { UnitState } from "../render/types"; +import { TrainType as RendererTrainType } from "../render/types"; +import { GameView } from "./GameView"; +import { PlayerView } from "./PlayerView"; + +/** + * Convert engine TrainType (string enum) to renderer's numeric encoding. + * UnitState uses 0/1/2 so it can be uploaded to GPU buffers without lookup. + */ +function trainTypeToNum(t: TrainType | undefined): number | null { + switch (t) { + case TrainType.Engine: + return RendererTrainType.Engine; + case TrainType.TailEngine: + return RendererTrainType.TailEngine; + case TrainType.Carriage: + return RendererTrainType.Carriage; + default: + return null; + } +} + +function numToTrainType(n: number | null): TrainType | undefined { + switch (n) { + case RendererTrainType.Engine: + return TrainType.Engine; + case RendererTrainType.TailEngine: + return TrainType.TailEngine; + case RendererTrainType.Carriage: + return TrainType.Carriage; + default: + return undefined; + } +} + +/** Build a fresh UnitState from an incoming UnitUpdate. */ +function unitStateFromUpdate(u: UnitUpdate): UnitState { + return { + id: u.id, + unitType: u.unitType, + ownerID: u.ownerID, + lastOwnerID: u.lastOwnerID ?? null, + pos: u.pos, + lastPos: u.lastPos, + isActive: u.isActive, + reachedTarget: u.reachedTarget, + retreating: u.transportShipState?.isRetreating ?? false, + targetable: u.targetable, + markedForDeletion: u.markedForDeletion, + health: u.health ?? null, + underConstruction: u.underConstruction ?? false, + targetUnitId: u.targetUnitId ?? null, + targetTile: u.targetTile ?? null, + troops: u.troops, + missileTimerQueue: u.missileTimerQueue, + level: u.level, + hasTrainStation: u.hasTrainStation, + trainType: trainTypeToNum(u.trainType), + loaded: u.loaded ?? null, + constructionStartTick: null, // GameView fills in createdAt when underConstruction + }; +} + +/** Mutate `target` in place from a UnitUpdate, avoiding any allocation. */ +function applyUpdateInPlace(target: UnitState, u: UnitUpdate): void { + target.ownerID = u.ownerID; + target.unitType = u.unitType; + target.lastOwnerID = u.lastOwnerID ?? null; + target.pos = u.pos; + target.lastPos = u.lastPos; + target.isActive = u.isActive; + target.reachedTarget = u.reachedTarget; + target.retreating = u.transportShipState?.isRetreating ?? false; + target.targetable = u.targetable; + target.markedForDeletion = u.markedForDeletion; + target.health = u.health ?? null; + target.underConstruction = u.underConstruction ?? false; + target.targetUnitId = u.targetUnitId ?? null; + target.targetTile = u.targetTile ?? null; + target.troops = u.troops; + target.missileTimerQueue = u.missileTimerQueue; + target.level = u.level; + target.hasTrainStation = u.hasTrainStation; + target.trainType = trainTypeToNum(u.trainType); + target.loaded = u.loaded ?? null; +} + +export class UnitView { + public _wasUpdated = true; + public lastPos: TileRef[] = []; + /** Long-lived renderer state — mutated in place by update(). */ + public state: UnitState; + /** Engine-only fields not in UnitState. Use warshipState() / transportShipState() to read. */ + private _warshipState?: WarshipState; + private _transportShipState?: TransportShipState; + private _createdAt: Tick; + + constructor( + private gameView: GameView, + data: UnitUpdate, + ) { + this.state = unitStateFromUpdate(data); + this._warshipState = data.warshipState; + this._transportShipState = data.transportShipState; + this.lastPos.push(data.pos); + this._createdAt = this.gameView.ticks(); + if (this.state.underConstruction) { + this.state.constructionStartTick = this._createdAt; + } + } + + createdAt(): Tick { + return this._createdAt; + } + + wasUpdated(): boolean { + return this._wasUpdated; + } + + lastTiles(): TileRef[] { + return this.lastPos; + } + + lastTile(): TileRef { + if (this.lastPos.length === 0) { + return this.state.pos; + } + return this.lastPos[0]; + } + + update(data: UnitUpdate) { + this.lastPos.push(data.pos); + this._wasUpdated = true; + const wasUnderConstruction = this.state.underConstruction; + applyUpdateInPlace(this.state, data); + this._warshipState = data.warshipState; + this._transportShipState = data.transportShipState; + // constructionStartTick: set on transition into underConstruction. + if (this.state.underConstruction && !wasUnderConstruction) { + this.state.constructionStartTick = this.gameView.ticks(); + } else if (!this.state.underConstruction) { + this.state.constructionStartTick = null; + } + } + + applyDerivedPosition(pos: TileRef) { + const prev = this.state.pos; + this.lastPos.push(pos); + this._wasUpdated = true; + this.state.lastPos = prev; + this.state.pos = pos; + } + + id(): number { + return this.state.id; + } + + targetable(): boolean { + return this.state.targetable; + } + + markedForDeletion(): number | false { + return this.state.markedForDeletion; + } + + type(): UnitType { + return this.state.unitType as UnitType; + } + troops(): number { + return this.state.troops; + } + warshipState(): WarshipState { + if (this._warshipState === undefined) { + throw new Error("warshipState called on non-warship unit"); + } + return this._warshipState; + } + updateWarshipState(_update: Partial): void { + throw new Error("updateWarshipState is not supported on UnitView"); + } + isInCombat(): boolean { + return this._warshipState?.isInCombat ?? false; + } + touch(): void { + throw new Error("touch is not supported on UnitView"); + } + transportShipState(): TransportShipState { + return this._transportShipState ?? { isRetreating: false, troops: 0 }; + } + updateTransportShipState( + _update: Pick, + ): void { + throw new Error("updateTransportShipState is not supported on UnitView"); + } + tile(): TileRef { + return this.state.pos; + } + owner(): PlayerView { + return this.gameView.playerBySmallID(this.state.ownerID)! as PlayerView; + } + isActive(): boolean { + return this.state.isActive; + } + reachedTarget(): boolean { + return this.state.reachedTarget; + } + hasHealth(): boolean { + return this.state.health !== null; + } + health(): number { + return this.state.health ?? 0; + } + isUnderConstruction(): boolean { + return this.state.underConstruction; + } + targetUnitId(): number | undefined { + return this.state.targetUnitId ?? undefined; + } + targetTile(): TileRef | undefined { + return this.state.targetTile ?? undefined; + } + + // How "ready" this unit is from 0 to 1. + missileReadinesss(): number { + const maxMissiles = this.state.level; + const missilesReloading = this.state.missileTimerQueue.length; + + if (missilesReloading === 0) { + return 1; + } + + const missilesReady = maxMissiles - missilesReloading; + + if (missilesReady === 0 && maxMissiles > 1) { + // Unless we have just one missile (level 1), + // show 0% readiness so user knows no missiles are ready. + return 0; + } + + let readiness = missilesReady / maxMissiles; + + const cooldownDuration = + this.state.unitType === UnitType.SAMLauncher + ? this.gameView.config().SAMCooldown() + : this.gameView.config().SiloCooldown(); + + for (const cooldown of this.state.missileTimerQueue) { + const cooldownProgress = this.gameView.ticks() - cooldown; + const cooldownRatio = cooldownProgress / cooldownDuration; + const adjusted = cooldownRatio / maxMissiles; + readiness += adjusted; + } + return readiness; + } + + level(): number { + return this.state.level; + } + hasTrainStation(): boolean { + return this.state.hasTrainStation; + } + trainType(): TrainType | undefined { + return numToTrainType(this.state.trainType); + } + isLoaded(): boolean | undefined { + return this.state.loaded ?? undefined; + } + missileTimerQueue(): number[] { + return this.state.missileTimerQueue; + } +} diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 75435cda82..509ce5dd09 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -1174,6 +1174,9 @@ export class GameImpl implements Game { tileState(tile: TileRef): number { return this._map.tileState(tile); } + tileStateBuffer(): Uint16Array { + return this._map.tileStateBuffer(); + } updateTile(tile: TileRef, state: number): boolean { return this._map.updateTile(tile, state); } diff --git a/src/core/game/GameMap.ts b/src/core/game/GameMap.ts index 592d02ca40..be403dcf18 100644 --- a/src/core/game/GameMap.ts +++ b/src/core/game/GameMap.ts @@ -72,6 +72,20 @@ export interface GameMap { */ updateTile(tile: TileRef, state: number): boolean; + /** + * Direct access to the per-tile state buffer for zero-copy consumers + * (e.g. WebGL renderer uploading to a R16UI texture). + * + * The returned array is a live reference — it is mutated by `updateTile()` + * each tick. Callers must not write to it. + * + * The bit layout of each `uint16` matches the renderer's tile state: + * bits 0-11: ownerID + * bit 13: fallout + * bit 14: defense bonus + */ + tileStateBuffer(): Uint16Array; + numTilesWithFallout(): number; } @@ -401,6 +415,10 @@ export class GameMapImpl implements GameMap { return this.state[tile]; } + tileStateBuffer(): Uint16Array { + return this.state; + } + /** * Update a tile from a packed uint32: * bits 0-15: tile state (owner, fallout, etc.) diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index a84f17b6a6..da408cfac1 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -1,1417 +1,12 @@ -import { Colord, colord } from "colord"; -import { base64url } from "jose"; -import { Config } from "../configuration/Config"; -import { ColorPalette } from "../CosmeticSchemas"; -import { PatternDecoder } from "../PatternDecoder"; -import { ClientID, GameID, Player, PlayerCosmetics } from "../Schemas"; -import { createRandomName, formatPlayerDisplayName } from "../Util"; -import { WorkerClient } from "../worker/WorkerClient"; -import { - BuildableUnit, - Cell, - EmojiMessage, - GameUpdates, - Gold, - NameViewData, - PlayerActions, - PlayerBorderTiles, - PlayerBuildableUnitType, - PlayerID, - PlayerProfile, - PlayerType, - Team, - TerrainType, - TerraNullius, - Tick, - TrainType, - TransportShipState, - Unit, - UnitInfo, - UnitType, - WarshipState, -} from "./Game"; -import { GameMap, TileRef } from "./GameMap"; -import { - AllianceView, - AttackUpdate, - GameUpdateType, - GameUpdateViewData, - PlayerUpdate, - SpawnPhaseEndUpdate, - UnitUpdate, -} from "./GameUpdates"; -import { MotionPlanRecord, unpackMotionPlans } from "./MotionPlans"; -import { TerrainMapData } from "./TerrainMapLoader"; -import { TerraNulliusImpl } from "./TerraNulliusImpl"; -import { UnitGrid, UnitPredicate } from "./UnitGrid"; -import { UserSettings } from "./UserSettings"; - -const userSettings: UserSettings = new UserSettings(); - -const FRIENDLY_TINT_TARGET = { r: 0, g: 255, b: 0, a: 1 }; -const EMBARGO_TINT_TARGET = { r: 255, g: 0, b: 0, a: 1 }; -const BORDER_TINT_RATIO = 0.35; - -export class UnitView { - public _wasUpdated = true; - public lastPos: TileRef[] = []; - private _createdAt: Tick; - - constructor( - private gameView: GameView, - private data: UnitUpdate, - ) { - this.lastPos.push(data.pos); - this._createdAt = this.gameView.ticks(); - } - - createdAt(): Tick { - return this._createdAt; - } - - wasUpdated(): boolean { - return this._wasUpdated; - } - - lastTiles(): TileRef[] { - return this.lastPos; - } - - lastTile(): TileRef { - if (this.lastPos.length === 0) { - return this.data.pos; - } - return this.lastPos[0]; - } - - update(data: UnitUpdate) { - this.lastPos.push(data.pos); - this._wasUpdated = true; - this.data = data; - } - - applyDerivedPosition(pos: TileRef) { - const prev = this.data.pos; - this.lastPos.push(pos); - this._wasUpdated = true; - this.data = { - ...this.data, - lastPos: prev, - pos, - }; - } - - id(): number { - return this.data.id; - } - - targetable(): boolean { - return this.data.targetable; - } - - markedForDeletion(): number | false { - return this.data.markedForDeletion; - } - - type(): UnitType { - return this.data.unitType; - } - troops(): number { - return this.data.troops; - } - warshipState(): WarshipState { - if (this.data.warshipState === undefined) { - throw new Error("warshipState called on non-warship unit"); - } - return this.data.warshipState; - } - updateWarshipState(_update: Partial): void { - throw new Error("updateWarshipState is not supported on UnitView"); - } - isInCombat(): boolean { - return this.data.warshipState?.isInCombat ?? false; - } - touch(): void { - throw new Error("touch is not supported on UnitView"); - } - transportShipState(): TransportShipState { - return this.data.transportShipState ?? { isRetreating: false, troops: 0 }; - } - updateTransportShipState( - _update: Pick, - ): void { - throw new Error("updateTransportShipState is not supported on UnitView"); - } - tile(): TileRef { - return this.data.pos; - } - owner(): PlayerView { - return this.gameView.playerBySmallID(this.data.ownerID)! as PlayerView; - } - isActive(): boolean { - return this.data.isActive; - } - reachedTarget(): boolean { - return this.data.reachedTarget; - } - hasHealth(): boolean { - return this.data.health !== undefined; - } - health(): number { - return this.data.health ?? 0; - } - isUnderConstruction(): boolean { - return this.data.underConstruction === true; - } - targetUnitId(): number | undefined { - return this.data.targetUnitId; - } - targetTile(): TileRef | undefined { - return this.data.targetTile; - } - - // How "ready" this unit is from 0 to 1. - missileReadinesss(): number { - const maxMissiles = this.data.level; - const missilesReloading = this.data.missileTimerQueue.length; - - if (missilesReloading === 0) { - return 1; - } - - const missilesReady = maxMissiles - missilesReloading; - - if (missilesReady === 0 && maxMissiles > 1) { - // Unless we have just one missile (level 1), - // show 0% readiness so user knows no missiles are ready. - return 0; - } - - let readiness = missilesReady / maxMissiles; - - const cooldownDuration = - this.data.unitType === UnitType.SAMLauncher - ? this.gameView.config().SAMCooldown() - : this.gameView.config().SiloCooldown(); - - for (const cooldown of this.data.missileTimerQueue) { - const cooldownProgress = this.gameView.ticks() - cooldown; - const cooldownRatio = cooldownProgress / cooldownDuration; - const adjusted = cooldownRatio / maxMissiles; - readiness += adjusted; - } - return readiness; - } - - level(): number { - return this.data.level; - } - hasTrainStation(): boolean { - return this.data.hasTrainStation; - } - trainType(): TrainType | undefined { - return this.data.trainType; - } - isLoaded(): boolean | undefined { - return this.data.loaded; - } - missileTimerQueue(): number[] { - return this.data.missileTimerQueue; - } -} - -export class PlayerView { - public anonymousName: string | null = null; - private decoder?: PatternDecoder; - - private _territoryColor: Colord; - private _borderColor: Colord; - // Update here to include structure light and dark colors - private _structureColors: { light: Colord; dark: Colord }; - - // Pre-computed border color variants - private _borderColorNeutral: Colord; - private _borderColorFriendly: Colord; - private _borderColorEmbargo: Colord; - private _borderColorDefendedNeutral: { light: Colord; dark: Colord }; - private _borderColorDefendedFriendly: { light: Colord; dark: Colord }; - private _borderColorDefendedEmbargo: { light: Colord; dark: Colord }; - - constructor( - private game: GameView, - public data: PlayerUpdate, - public nameData: NameViewData, - public cosmetics: PlayerCosmetics, - ) { - if (data.clientID === game.myClientID()) { - this.anonymousName = this.data.name; - } else { - this.anonymousName = createRandomName( - this.data.name, - this.data.playerType, - ); - } - - const theme = this.game.config().theme(); - - const defaultTerritoryColor = theme.territoryColor(this); - const defaultBorderColor = theme.borderColor(defaultTerritoryColor); - - const pattern = userSettings.territoryPatterns() - ? this.cosmetics.pattern - : undefined; - if (pattern) { - pattern.colorPalette ??= { - name: "", - primaryColor: defaultTerritoryColor.toHex(), - secondaryColor: defaultBorderColor.toHex(), - } satisfies ColorPalette; - } - - if (this.team() === null) { - this._territoryColor = colord( - this.cosmetics.color?.color ?? - pattern?.colorPalette?.primaryColor ?? - defaultTerritoryColor.toHex(), - ); - } else { - this._territoryColor = defaultTerritoryColor; - } - - this._structureColors = theme.structureColors(this._territoryColor); - - const maybeFocusedBorderColor = - this.game.myClientID() === this.data.clientID - ? theme.focusedBorderColor() - : defaultBorderColor; - - this._borderColor = new Colord( - pattern?.colorPalette?.secondaryColor ?? - this.cosmetics.color?.color ?? - maybeFocusedBorderColor.toHex(), - ); - - // Pre-compute all border color variants once - const baseRgb = this._borderColor.toRgb(); - - // Neutral is just the base color - this._borderColorNeutral = this._borderColor; - - // Compute friendly tint - this._borderColorFriendly = colord({ - r: Math.round( - baseRgb.r * (1 - BORDER_TINT_RATIO) + - FRIENDLY_TINT_TARGET.r * BORDER_TINT_RATIO, - ), - g: Math.round( - baseRgb.g * (1 - BORDER_TINT_RATIO) + - FRIENDLY_TINT_TARGET.g * BORDER_TINT_RATIO, - ), - b: Math.round( - baseRgb.b * (1 - BORDER_TINT_RATIO) + - FRIENDLY_TINT_TARGET.b * BORDER_TINT_RATIO, - ), - a: baseRgb.a, - }); - - // Compute embargo tint - this._borderColorEmbargo = colord({ - r: Math.round( - baseRgb.r * (1 - BORDER_TINT_RATIO) + - EMBARGO_TINT_TARGET.r * BORDER_TINT_RATIO, - ), - g: Math.round( - baseRgb.g * (1 - BORDER_TINT_RATIO) + - EMBARGO_TINT_TARGET.g * BORDER_TINT_RATIO, - ), - b: Math.round( - baseRgb.b * (1 - BORDER_TINT_RATIO) + - EMBARGO_TINT_TARGET.b * BORDER_TINT_RATIO, - ), - a: baseRgb.a, - }); - - // Pre-compute defended variants - this._borderColorDefendedNeutral = theme.defendedBorderColors( - this._borderColorNeutral, - ); - this._borderColorDefendedFriendly = theme.defendedBorderColors( - this._borderColorFriendly, - ); - this._borderColorDefendedEmbargo = theme.defendedBorderColors( - this._borderColorEmbargo, - ); - - this.decoder = - pattern === undefined - ? undefined - : new PatternDecoder(pattern, base64url.decode); - } - - territoryColor(tile?: TileRef): Colord { - if (tile === undefined || this.decoder === undefined) { - return this._territoryColor; - } - const isPrimary = this.decoder.isPrimary( - this.game.x(tile), - this.game.y(tile), - ); - return isPrimary ? this._territoryColor : this._borderColor; - } - - structureColors(): { light: Colord; dark: Colord } { - return this._structureColors; - } - - /** - * Border color for a tile: - * - Tints by neighbor relations (embargo → red, friendly → green, else neutral). - * - If defended, applies theme checkerboard to the tinted color. - */ - borderColor(tile?: TileRef, isDefended: boolean = false): Colord { - if (tile === undefined) { - return this._borderColor; - } - - const { hasEmbargo, hasFriendly } = this.borderRelationFlags(tile); - - let baseColor: Colord; - let defendedColors: { light: Colord; dark: Colord }; - - if (hasEmbargo) { - baseColor = this._borderColorEmbargo; - defendedColors = this._borderColorDefendedEmbargo; - } else if (hasFriendly) { - baseColor = this._borderColorFriendly; - defendedColors = this._borderColorDefendedFriendly; - } else { - baseColor = this._borderColorNeutral; - defendedColors = this._borderColorDefendedNeutral; - } - - if (!isDefended) { - return baseColor; - } - - const x = this.game.x(tile); - const y = this.game.y(tile); - const lightTile = - (x % 2 === 0 && y % 2 === 0) || (y % 2 === 1 && x % 2 === 1); - return lightTile ? defendedColors.light : defendedColors.dark; - } - - /** - * Border relation flags for a tile, used by both CPU and WebGL renderers. - */ - borderRelationFlags(tile: TileRef): { - hasEmbargo: boolean; - hasFriendly: boolean; - } { - const mySmallID = this.smallID(); - let hasEmbargo = false; - let hasFriendly = false; - - for (const n of this.game.neighbors(tile)) { - if (!this.game.hasOwner(n)) { - continue; - } - - const otherOwner = this.game.owner(n); - if (!otherOwner.isPlayer() || otherOwner.smallID() === mySmallID) { - continue; - } - - if (this.hasEmbargo(otherOwner)) { - hasEmbargo = true; - break; - } - - if (this.isFriendly(otherOwner) || otherOwner.isFriendly(this)) { - hasFriendly = true; - } - } - return { hasEmbargo, hasFriendly }; - } - - async actions( - tile?: TileRef, - units?: readonly PlayerBuildableUnitType[] | null, - ): Promise { - return this.game.worker.playerInteraction( - this.id(), - tile && this.game.x(tile), - tile && this.game.y(tile), - units, - ); - } - - async buildables( - tile?: TileRef, - units?: readonly PlayerBuildableUnitType[], - ): Promise { - return this.game.worker.playerBuildables( - this.id(), - tile && this.game.x(tile), - tile && this.game.y(tile), - units, - ); - } - - async borderTiles(): Promise { - return this.game.worker.playerBorderTiles(this.id()); - } - - outgoingAttacks(): AttackUpdate[] { - return this.data.outgoingAttacks; - } - - incomingAttacks(): AttackUpdate[] { - return this.data.incomingAttacks; - } - - async attackClusteredPositions( - attackID?: string, - ): Promise<{ id: string; positions: Cell[] }[]> { - return this.game.worker.attackClusteredPositions(this.smallID(), attackID); - } - - units(...types: UnitType[]): UnitView[] { - return this.game - .units(...types) - .filter((u) => u.owner().smallID() === this.smallID()); - } - - nameLocation(): NameViewData { - return this.nameData; - } - - smallID(): number { - return this.data.smallID; - } - - name(): string { - return this.anonymousName !== null && userSettings.anonymousNames() - ? this.anonymousName - : this.data.name; - } - displayName(): string { - return this.anonymousName !== null && userSettings.anonymousNames() - ? this.anonymousName - : this.data.displayName; - } - - clientID(): ClientID | null { - return this.data.clientID; - } - id(): PlayerID { - return this.data.id; - } - team(): Team | null { - return this.data.team ?? null; - } - type(): PlayerType { - return this.data.playerType; - } - isAlive(): boolean { - return this.data.isAlive; - } - isPlayer(): this is PlayerView { - return true; - } - numTilesOwned(): number { - return this.data.tilesOwned; - } - allies(): PlayerView[] { - return this.data.allies.map( - (a) => this.game.playerBySmallID(a) as PlayerView, - ); - } - targets(): PlayerView[] { - return this.data.targets.map( - (id) => this.game.playerBySmallID(id) as PlayerView, - ); - } - gold(): Gold { - return this.data.gold; - } - - troops(): number { - return this.data.troops; - } - - totalUnitLevels(type: UnitType): number { - return this.units(type) - .filter((unit) => !unit.isUnderConstruction()) - .map((unit) => unit.level()) - .reduce((a, b) => a + b, 0); - } - - isMe(): boolean { - return this.smallID() === this.game.myPlayer()?.smallID(); - } - - isLobbyCreator(): boolean { - return this.data.isLobbyCreator; - } - - isAlliedWith(other: PlayerView): boolean { - return this.data.allies.some((n) => other.smallID() === n); - } - - isOnSameTeam(other: PlayerView): boolean { - return this.data.team !== undefined && this.data.team === other.data.team; - } - - isFriendly(other: PlayerView): boolean { - return this.isAlliedWith(other) || this.isOnSameTeam(other); - } - - isRequestingAllianceWith(other: PlayerView) { - return this.data.outgoingAllianceRequests.some((id) => other.id() === id); - } - - alliances(): AllianceView[] { - return this.data.alliances; - } - - hasEmbargoAgainst(other: PlayerView): boolean { - return this.data.embargoes.has(other.id()); - } - - hasEmbargo(other: PlayerView): boolean { - return this.hasEmbargoAgainst(other) || other.hasEmbargoAgainst(this); - } - - profile(): Promise { - return this.game.worker.playerProfile(this.smallID()); - } - - bestTransportShipSpawn(targetTile: TileRef): Promise { - return this.game.worker.transportShipSpawn(this.id(), targetTile); - } - - transitiveTargets(): PlayerView[] { - const result: PlayerView[] = []; - - // Add own targets - for (const id of this.data.targets) { - result.push(this.game.playerBySmallID(id) as PlayerView); - } - - // Add allies' targets - for (const allyID of this.data.allies) { - const ally = this.game.playerBySmallID(allyID) as PlayerView; - for (const targetId of ally.data.targets) { - result.push(this.game.playerBySmallID(targetId) as PlayerView); - } - } - - // Add teammates' targets - if (this.data.team !== undefined) { - for (const p of this.game.playerViews()) { - if (p !== this && p.data.team === this.data.team) { - for (const targetId of p.data.targets) { - result.push(this.game.playerBySmallID(targetId) as PlayerView); - } - } - } - } - - return result; - } - - isTraitor(): boolean { - return this.data.isTraitor; - } - getTraitorRemainingTicks(): number { - return Math.max(0, this.data.traitorRemainingTicks ?? 0); - } - outgoingEmojis(): EmojiMessage[] { - return this.data.outgoingEmojis; - } - - hasSpawned(): boolean { - return this.data.hasSpawned; - } - isDisconnected(): boolean { - return this.data.isDisconnected; - } - - lastDeleteUnitTick(): Tick { - return this.data.lastDeleteUnitTick; - } - - deleteUnitCooldown(): number { - return ( - Math.max( - 0, - this.game.config().deleteUnitCooldown() - - (this.game.ticks() + 1 - this.lastDeleteUnitTick()), - ) / 10 - ); - } -} - -type TrainPlanState = { - planId: number; - startTick: number; - speed: number; - spacing: number; - carUnitIds: Uint32Array; - path: Uint32Array; - cursor: number; - usedTilesBuf: Uint32Array; - usedHead: number; - usedLen: number; - lastAdvancedTick: Tick; -}; - -export class GameView implements GameMap { - private lastUpdate: GameUpdateViewData | null; - private startTick: Tick | null = null; - private smallIDToID = new Map(); - private _players = new Map(); - private _units = new Map(); - private updatedTiles: TileRef[] = []; - private updatedTerrainTiles: TileRef[] = []; - - private _myPlayer: PlayerView | null = null; - - private unitGrid: UnitGrid; - private unitMotionPlans = new Map< - number, - { - planId: number; - startTick: number; - ticksPerStep: number; - path: Uint32Array; - } - >(); - private trainMotionPlans = new Map(); - private trainUnitToEngine = new Map(); - - private toDelete = new Set(); - - private _cosmetics: Map = new Map(); - - private _map: GameMap; - - constructor( - public worker: WorkerClient, - private _config: Config, - private _mapData: TerrainMapData, - private _myClientID: ClientID | undefined, - private _myUsername: string, - private _myClanTag: string | null, - private _gameID: GameID, - humans: Player[], - ) { - this._map = this._mapData.gameMap; - this.lastUpdate = null; - this.unitGrid = new UnitGrid(this._map); - this._cosmetics = new Map( - humans.map((h) => [h.clientID, h.cosmetics ?? {}]), - ); - for (const nation of this._mapData.nations) { - // Nations don't have client ids, so we use their name as the key instead. - this._cosmetics.set(nation.name, { - flag: nation.flag ? `/flags/${nation.flag}.svg` : undefined, - } satisfies PlayerCosmetics); - } - for (const extra of this._mapData.additionalNations) { - // Only set if not already provided by a manifest nation with the same name. - if (this._cosmetics.has(extra.name)) continue; - this._cosmetics.set(extra.name, { - flag: extra.flag ? `/flags/${extra.flag}.svg` : undefined, - } satisfies PlayerCosmetics); - } - } - - isOnEdgeOfMap(ref: TileRef): boolean { - return this._map.isOnEdgeOfMap(ref); - } - - public updatesSinceLastTick(): GameUpdates | null { - return this.lastUpdate?.updates ?? null; - } - - public motionPlans(): ReadonlyMap< - number, - { - planId: number; - startTick: number; - ticksPerStep: number; - path: Uint32Array; - } - > { - return this.unitMotionPlans; - } - - private motionPlannedUnitIdsCache: number[] = []; - private motionPlannedUnitIdsDirty = true; - - private markMotionPlannedUnitIdsDirty(): void { - this.motionPlannedUnitIdsDirty = true; - } - - private rebuildMotionPlannedUnitIdsCacheIfDirty(): void { - if (!this.motionPlannedUnitIdsDirty) { - return; - } - this.motionPlannedUnitIdsDirty = false; - - const out = this.motionPlannedUnitIdsCache; - out.length = 0; - - for (const unitId of this.unitMotionPlans.keys()) { - out.push(unitId); - } - for (const [engineId, plan] of this.trainMotionPlans) { - out.push(engineId); - for (let i = 0; i < plan.carUnitIds.length; i++) { - const id = plan.carUnitIds[i] >>> 0; - if (id !== 0) out.push(id); - } - } - } - - public motionPlannedUnitIds(): number[] { - this.rebuildMotionPlannedUnitIdsCacheIfDirty(); - return this.motionPlannedUnitIdsCache; - } - - public isCatchingUp(): boolean { - return (this.lastUpdate?.pendingTurns ?? 0) > 1; - } - - public update(gu: GameUpdateViewData) { - this.toDelete.forEach((id) => this._units.delete(id)); - this.toDelete.clear(); - - this.lastUpdate = gu; - - this.updatedTiles = []; - this.updatedTerrainTiles = []; - const packed = this.lastUpdate.packedTileUpdates; - for (let i = 0; i + 1 < packed.length; i += 2) { - const tile = packed[i]; - const state = packed[i + 1]; - const terrainChanged = this.updateTile(tile, state); - this.updatedTiles.push(tile); - if (terrainChanged) { - this.updatedTerrainTiles.push(tile); - } - } - - if (gu.packedMotionPlans) { - const records = unpackMotionPlans(gu.packedMotionPlans); - this.applyMotionPlanRecords(records); - } - - if (gu.updates === null) { - throw new Error("lastUpdate.updates not initialized"); - } - - const spawnPhaseEndUpdate = gu.updates[GameUpdateType.SpawnPhaseEnd][0] as - | SpawnPhaseEndUpdate - | undefined; - if (spawnPhaseEndUpdate) { - this.startTick = spawnPhaseEndUpdate.startTick; - } - - const myDisplayName = formatPlayerDisplayName( - this._myUsername, - this._myClanTag, - ); - - gu.updates[GameUpdateType.Player].forEach((pu) => { - // Replace the local player's name/displayName with their own stored values. - // This way the user does not know they are being censored. - if (pu.clientID === this._myClientID) { - pu.name = this._myUsername; - pu.displayName = myDisplayName; - } - - this.smallIDToID.set(pu.smallID, pu.id); - let player = this._players.get(pu.id); - if (player !== undefined) { - player.data = pu; - const nextNameData = gu.playerNameViewData[pu.id]; - if (nextNameData !== undefined) { - player.nameData = nextNameData; - } - } else { - player = new PlayerView( - this, - pu, - gu.playerNameViewData[pu.id], - // First check human by clientID, then check nation by name. - this._cosmetics.get(pu.clientID ?? "") ?? - this._cosmetics.get(pu.name) ?? - {}, - ); - this._players.set(pu.id, player); - } - }); - - if (this._myClientID) { - this._myPlayer ??= this.playerByClientID(this._myClientID); - } - - for (const unit of this._units.values()) { - unit._wasUpdated = false; - unit.lastPos = unit.lastPos.slice(-1); - } - gu.updates[GameUpdateType.Unit].forEach((update) => { - let unit = this._units.get(update.id); - if (unit !== undefined) { - unit.update(update); - } else { - unit = new UnitView(this, update); - this._units.set(update.id, unit); - this.unitGrid.addUnit(unit); - } - if (!update.isActive) { - this.unitGrid.removeUnit(unit); - } else if (unit.tile() !== unit.lastTile()) { - this.unitGrid.updateUnitCell(unit); - } - if (!unit.isActive()) { - // Wait until next tick to delete the unit. - this.toDelete.add(unit.id()); - if (this.unitMotionPlans.delete(unit.id())) { - this.markMotionPlannedUnitIdsDirty(); - } - this.clearTrainPlanForUnit(unit.id()); - } - }); - - this.advanceMotionPlannedUnits(gu.tick); - this.rebuildMotionPlannedUnitIdsCacheIfDirty(); - } - - private advanceMotionPlannedUnits(currentTick: Tick): void { - for (const [unitId, plan] of this.unitMotionPlans) { - const unit = this._units.get(unitId); - if (!unit || !unit.isActive()) { - if (this.unitMotionPlans.delete(unitId)) { - this.markMotionPlannedUnitIdsDirty(); - } - continue; - } - - const oldTile = unit.tile(); - const dt = currentTick - plan.startTick; - const stepIndex = - dt <= 0 ? 0 : Math.floor(dt / Math.max(1, plan.ticksPerStep)); - const lastIndex = plan.path.length - 1; - const idx = Math.max(0, Math.min(lastIndex, stepIndex)); - const newTile = plan.path[idx] as TileRef; - - if (newTile !== oldTile) { - unit.applyDerivedPosition(newTile); - this.unitGrid.updateUnitCell(unit); - continue; - } - - // Once a plan is past its final step, `newTile` remains clamped to the last path tile. - // Drop finished plans to avoid repeatedly marking static units as updated each tick. - if (dt > 0 && stepIndex >= lastIndex) { - if (this.unitMotionPlans.delete(unitId)) { - this.markMotionPlannedUnitIdsDirty(); - } - } - } - - this.advanceTrainMotionPlannedUnits(currentTick); - } - - private clearTrainPlanForUnit(unitId: number): void { - const engineId = - this.trainUnitToEngine.get(unitId) ?? - (this.trainMotionPlans.has(unitId) ? unitId : null); - if (engineId === null) { - return; - } - const plan = this.trainMotionPlans.get(engineId); - if (!plan) { - this.trainUnitToEngine.delete(unitId); - return; - } - if (this.trainMotionPlans.delete(engineId)) { - this.markMotionPlannedUnitIdsDirty(); - } - this.trainUnitToEngine.delete(engineId); - for (let i = 0; i < plan.carUnitIds.length; i++) { - const id = plan.carUnitIds[i] >>> 0; - if (id !== 0) this.trainUnitToEngine.delete(id); - } - } - - private advanceTrainMotionPlannedUnits(currentTick: Tick): void { - const staleEngineIds: number[] = []; - for (const [engineId, plan] of this.trainMotionPlans) { - const engine = this._units.get(engineId); - if (!engine || !engine.isActive()) { - staleEngineIds.push(engineId); - continue; - } - - const steps = currentTick - plan.lastAdvancedTick; - if (steps <= 0) { - continue; - } - - const path = plan.path; - const lastIndex = path.length - 1; - const cap = plan.usedTilesBuf.length; - - const pushUsed = (tile: TileRef) => { - if (cap === 0) return; - if (plan.usedLen < cap) { - const idx = (plan.usedHead + plan.usedLen) % cap; - plan.usedTilesBuf[idx] = tile >>> 0; - plan.usedLen++; - } else { - plan.usedTilesBuf[plan.usedHead] = tile >>> 0; - plan.usedHead = (plan.usedHead + 1) % cap; - plan.usedLen = cap; - } - }; - - const usedGet = (index: number): TileRef | null => { - if (index < 0 || index >= plan.usedLen || cap === 0) return null; - const idx = (plan.usedHead + index) % cap; - return plan.usedTilesBuf[idx] as TileRef; - }; - - let didMove = false; - for (let step = 0; step < steps; step++) { - const cursor = plan.cursor; - if (cursor >= lastIndex) { - break; - } - for (let i = 0; i < plan.speed && cursor + i < path.length; i++) { - pushUsed(path[cursor + i] as TileRef); - } - - plan.cursor = Math.min(lastIndex, cursor + plan.speed); - - for (let i = plan.carUnitIds.length - 1; i >= 0; --i) { - const carId = plan.carUnitIds[i] >>> 0; - if (carId === 0) continue; - const car = this._units.get(carId); - if (!car || !car.isActive()) { - continue; - } - const carTileIndex = (i + 1) * plan.spacing + 2; - const tile = usedGet(carTileIndex); - if (tile !== null) { - const oldTile = car.tile(); - if (tile !== oldTile) { - car.applyDerivedPosition(tile); - this.unitGrid.updateUnitCell(car); - didMove = true; - } - } - } - - const newEngineTile = path[plan.cursor] as TileRef; - const oldEngineTile = engine.tile(); - if (newEngineTile !== oldEngineTile) { - engine.applyDerivedPosition(newEngineTile); - this.unitGrid.updateUnitCell(engine); - didMove = true; - } - } - - plan.lastAdvancedTick = currentTick; - - // Preserve the final-step redraw (plan remains for the tick where motion ends), - // then clear once the train has settled and no longer moves. - // Note: trains are currently deleted at the end of TrainExecution, and the ensuing - // `Unit` update (isActive=false) also clears any associated motion plan records. - // This expiry is defensive to avoid keeping stale plans around if that behavior changes. - if (!didMove && plan.cursor >= lastIndex) { - staleEngineIds.push(engineId); - } - } - - for (const engineId of staleEngineIds) { - this.clearTrainPlanForUnit(engineId); - } - } - - private applyMotionPlanRecords(records: readonly MotionPlanRecord[]): void { - for (const record of records) { - switch (record.kind) { - case "grid": { - if (record.ticksPerStep < 1 || record.path.length < 1) { - break; - } - const existing = this.unitMotionPlans.get(record.unitId); - if (existing && record.planId <= existing.planId) { - break; - } - - const path = - record.path instanceof Uint32Array - ? record.path - : Uint32Array.from(record.path); - - this.unitMotionPlans.set(record.unitId, { - planId: record.planId, - startTick: record.startTick, - ticksPerStep: record.ticksPerStep, - path, - }); - this.markMotionPlannedUnitIdsDirty(); - break; - } - case "train": { - if (record.speed < 1 || record.path.length < 1) { - break; - } - const existing = this.trainMotionPlans.get(record.engineUnitId); - if (existing && record.planId <= existing.planId) { - break; - } - if (existing) { - this.clearTrainPlanForUnit(record.engineUnitId); - } - - const carUnitIds = - record.carUnitIds instanceof Uint32Array - ? record.carUnitIds - : Uint32Array.from(record.carUnitIds); - const path = - record.path instanceof Uint32Array - ? record.path - : Uint32Array.from(record.path); - - const usedCap = carUnitIds.length * record.spacing + 3; - const usedTilesBuf = new Uint32Array(Math.max(0, usedCap)); - - this.trainMotionPlans.set(record.engineUnitId, { - planId: record.planId, - startTick: record.startTick, - speed: record.speed, - spacing: record.spacing, - carUnitIds, - path, - cursor: 0, - usedTilesBuf, - usedHead: 0, - usedLen: 0, - lastAdvancedTick: record.startTick, - }); - this.markMotionPlannedUnitIdsDirty(); - - this.trainUnitToEngine.set(record.engineUnitId, record.engineUnitId); - for (let i = 0; i < carUnitIds.length; i++) { - const carId = carUnitIds[i] >>> 0; - if (carId !== 0) - this.trainUnitToEngine.set(carId, record.engineUnitId); - } - break; - } - } - } - } - - recentlyUpdatedTiles(): TileRef[] { - return this.updatedTiles; - } - - recentlyUpdatedTerrainTiles(): TileRef[] { - return this.updatedTerrainTiles; - } - - nearbyUnits( - tile: TileRef, - searchRange: number, - types: UnitType | readonly UnitType[], - predicate?: UnitPredicate, - ): Array<{ unit: UnitView; distSquared: number }> { - return this.unitGrid.nearbyUnits( - tile, - searchRange, - types, - predicate, - ) as Array<{ - unit: UnitView; - distSquared: number; - }>; - } - - hasUnitNearby( - tile: TileRef, - searchRange: number, - type: UnitType, - playerId?: PlayerID, - includeUnderConstruction?: boolean, - ) { - return this.unitGrid.hasUnitNearby( - tile, - searchRange, - type, - playerId, - includeUnderConstruction, - ); - } - - anyUnitNearby( - tile: TileRef, - searchRange: number, - types: readonly UnitType[], - predicate: (unit: UnitView) => boolean, - playerId?: PlayerID, - includeUnderConstruction?: boolean, - ): boolean { - return this.unitGrid.anyUnitNearby( - tile, - searchRange, - types, - predicate as (unit: Unit | UnitView) => boolean, - playerId, - includeUnderConstruction, - ); - } - - myClientID(): ClientID | undefined { - return this._myClientID; - } - - myPlayer(): PlayerView | null { - return this._myPlayer; - } - - player(id: PlayerID): PlayerView { - const player = this._players.get(id); - if (player === undefined) { - throw Error(`player id ${id} not found`); - } - return player; - } - - players(): PlayerView[] { - return Array.from(this._players.values()); - } - - playerBySmallID(id: number): PlayerView | TerraNullius { - if (id === 0) { - return new TerraNulliusImpl(); - } - const playerId = this.smallIDToID.get(id); - if (playerId === undefined) { - throw new Error(`small id ${id} not found`); - } - return this.player(playerId); - } - - playerByClientID(id: ClientID): PlayerView | null { - const player = - Array.from(this._players.values()).filter( - (p) => p.clientID() === id, - )[0] ?? null; - if (player === null) { - return null; - } - return player; - } - hasPlayer(id: PlayerID): boolean { - return false; - } - playerViews(): PlayerView[] { - return Array.from(this._players.values()); - } - - owner(tile: TileRef): PlayerView | TerraNullius { - return this.playerBySmallID(this.ownerID(tile)); - } - - ticks(): Tick { - if (this.lastUpdate === null) return 0; - return this.lastUpdate.tick; - } - inSpawnPhase(): boolean { - return this.startTick === null; - } - - isSpawnImmunityActive(): boolean { - return ( - this.inSpawnPhase() || - this.ticksSinceStart() < this._config.spawnImmunityDuration() - ); - } - isNationSpawnImmunityActive(): boolean { - return ( - this.inSpawnPhase() || - this.ticksSinceStart() < this._config.nationSpawnImmunityDuration() - ); - } - - elapsedGameSeconds(): number { - return this.ticksSinceStart() / 10; - } - - ticksSinceStart(): Tick { - if (this.inSpawnPhase()) { - return 0; - } - - return Math.max(0, this.ticks() - this.startTick!); - } - config(): Config { - return this._config; - } - units(...types: UnitType[]): UnitView[] { - if (types.length === 0) { - return Array.from(this._units.values()).filter((u) => u.isActive()); - } - return Array.from(this._units.values()).filter( - (u) => u.isActive() && types.includes(u.type()), - ); - } - unit(id: number): UnitView | undefined { - return this._units.get(id); - } - unitInfo(type: UnitType): UnitInfo { - return this._config.unitInfo(type); - } - - ref(x: number, y: number): TileRef { - return this._map.ref(x, y); - } - isValidRef(ref: TileRef): boolean { - return this._map.isValidRef(ref); - } - x(ref: TileRef): number { - return this._map.x(ref); - } - y(ref: TileRef): number { - return this._map.y(ref); - } - cell(ref: TileRef): Cell { - return this._map.cell(ref); - } - width(): number { - return this._map.width(); - } - height(): number { - return this._map.height(); - } - numLandTiles(): number { - return this._map.numLandTiles(); - } - isValidCoord(x: number, y: number): boolean { - return this._map.isValidCoord(x, y); - } - isLand(ref: TileRef): boolean { - return this._map.isLand(ref); - } - isOceanShore(ref: TileRef): boolean { - return this._map.isOceanShore(ref); - } - isOcean(ref: TileRef): boolean { - return this._map.isOcean(ref); - } - isShoreline(ref: TileRef): boolean { - return this._map.isShoreline(ref); - } - magnitude(ref: TileRef): number { - return this._map.magnitude(ref); - } - terrainByte(ref: TileRef): number { - return this._map.terrainByte(ref); - } - setWater(ref: TileRef): void { - this._map.setWater(ref); - } - setShorelineBit(ref: TileRef): void { - this._map.setShorelineBit(ref); - } - clearShorelineBit(ref: TileRef): void { - this._map.clearShorelineBit(ref); - } - setOcean(ref: TileRef): void { - this._map.setOcean(ref); - } - setMagnitude(ref: TileRef, value: number): void { - this._map.setMagnitude(ref, value); - } - ownerID(ref: TileRef): number { - return this._map.ownerID(ref); - } - hasOwner(ref: TileRef): boolean { - return this._map.hasOwner(ref); - } - setOwnerID(ref: TileRef, playerId: number): void { - return this._map.setOwnerID(ref, playerId); - } - hasFallout(ref: TileRef): boolean { - return this._map.hasFallout(ref); - } - setFallout(ref: TileRef, value: boolean): void { - return this._map.setFallout(ref, value); - } - isBorder(ref: TileRef): boolean { - return this._map.isBorder(ref); - } - neighbors(ref: TileRef): TileRef[] { - return this._map.neighbors(ref); - } - isWater(ref: TileRef): boolean { - return this._map.isWater(ref); - } - isLake(ref: TileRef): boolean { - return this._map.isLake(ref); - } - isShore(ref: TileRef): boolean { - return this._map.isShore(ref); - } - cost(ref: TileRef): number { - return this._map.cost(ref); - } - terrainType(ref: TileRef): TerrainType { - return this._map.terrainType(ref); - } - forEachTile(fn: (tile: TileRef) => void): void { - return this._map.forEachTile(fn); - } - manhattanDist(c1: TileRef, c2: TileRef): number { - return this._map.manhattanDist(c1, c2); - } - euclideanDistSquared(c1: TileRef, c2: TileRef): number { - return this._map.euclideanDistSquared(c1, c2); - } - circleSearch( - tile: TileRef, - radius: number, - filter?: (tile: TileRef, d2: number) => boolean, - ): Set { - return this._map.circleSearch(tile, radius, filter); - } - bfs( - tile: TileRef, - filter: (gm: GameMap, tile: TileRef) => boolean, - ): Set { - return this._map.bfs(tile, filter); - } - tileState(tile: TileRef): number { - return this._map.tileState(tile); - } - updateTile(tile: TileRef, state: number): boolean { - return this._map.updateTile(tile, state); - } - numTilesWithFallout(): number { - return this._map.numTilesWithFallout(); - } - gameID(): GameID { - return this._gameID; - } - - focusedPlayer(): PlayerView | null { - return this.myPlayer(); - } -} +// Back-compat re-export shim. +// The view classes physically live in src/client/view/ — this re-export keeps +// the older `import { GameView } from "src/core/game/GameView"` path working. +// +// TODO: remove this shim once all 50+ importers have been updated to point at +// src/client/view/ directly, and the 6 core files that reference PlayerView / +// UnitView / GameView as union types (Player | PlayerView etc.) are refactored +// to use Player / Unit / Game interfaces instead. + +export { GameView } from "../../client/view/GameView"; +export { PlayerView } from "../../client/view/PlayerView"; +export { UnitView } from "../../client/view/UnitView"; diff --git a/tests/client/view/GameView.test.ts b/tests/client/view/GameView.test.ts new file mode 100644 index 0000000000..39347d4d89 --- /dev/null +++ b/tests/client/view/GameView.test.ts @@ -0,0 +1,474 @@ +/** + * GameView is the client-side simulation mirror — it accumulates player / + * unit / tile state from per-tick GameUpdateViewData. The FrameBuilder reads + * the same accessors (players(), units(), tileStateBuffer(), + * recentlyUpdatedTiles()) to translate state into FrameData each tick. + * + * These tests verify the update lifecycle: PlayerView reuse vs creation, + * UnitView lifecycle (create / mutate / mark for deletion / sweep next tick), + * smallID lookup, tick tracking, and tile delta accumulation. + */ + +import { describe, expect, it } from "vitest"; +import { UnitType } from "../../../src/core/game/Game"; +import { GameUpdateType } from "../../../src/core/game/GameUpdates"; +import { + makeEmptyGu, + makeGameView, + makeNameViewData, + makePlayerUpdate, + makeUnitUpdate, +} from "../../util/viewStubs"; + +function withPlayers( + tick: number, + players: ReturnType[], + nameDataMap: Record> = {}, +) { + const gu = makeEmptyGu(tick); + gu.updates[GameUpdateType.Player] = players; + for (const p of players) { + gu.playerNameViewData[p.id] = nameDataMap[p.id] ?? makeNameViewData(); + } + return gu; +} + +describe("GameView.update — players", () => { + it("creates a PlayerView for each player in the first tick", () => { + const game = makeGameView(); + game.update( + withPlayers(1, [ + makePlayerUpdate({ id: "alice", smallID: 1, name: "Alice" }), + makePlayerUpdate({ id: "bob", smallID: 2, name: "Bob" }), + ]), + ); + expect(game.players().map((p) => p.id())).toEqual(["alice", "bob"]); + }); + + it("reuses an existing PlayerView on subsequent updates (in-place data swap)", () => { + const game = makeGameView(); + game.update( + withPlayers(1, [ + makePlayerUpdate({ id: "alice", smallID: 1, troops: 100 }), + ]), + ); + const first = game.player("alice"); + + game.update( + withPlayers(2, [ + makePlayerUpdate({ id: "alice", smallID: 1, troops: 250 }), + ]), + ); + const second = game.player("alice"); + + expect(second).toBe(first); // same PlayerView instance + expect(second.troops()).toBe(250); // data was swapped in + }); + + it("playerBySmallID resolves through the smallID → PlayerID map", () => { + const game = makeGameView(); + game.update( + withPlayers(1, [ + makePlayerUpdate({ id: "alice", smallID: 1 }), + makePlayerUpdate({ id: "bob", smallID: 2 }), + ]), + ); + expect( + (game.playerBySmallID(1) as ReturnType).id(), + ).toBe("alice"); + expect( + (game.playerBySmallID(2) as ReturnType).id(), + ).toBe("bob"); + }); + + it("playerBySmallID(0) returns a TerraNullius (used as the unowned-tile owner)", () => { + const game = makeGameView(); + const terra = game.playerBySmallID(0); + expect(terra.isPlayer()).toBe(false); + }); + + it("myPlayer() is resolved once the local player update arrives", () => { + const game = makeGameView({ myClientID: "c-me" }); + expect(game.myPlayer()).toBeNull(); + + game.update( + withPlayers(1, [ + makePlayerUpdate({ + id: "me", + smallID: 1, + clientID: "c-me", + name: "Me", + }), + ]), + ); + expect(game.myPlayer()?.id()).toBe("me"); + }); + + it("myPlayer() is cached — does not change identity across updates", () => { + const game = makeGameView({ myClientID: "c-me" }); + game.update( + withPlayers(1, [ + makePlayerUpdate({ id: "me", smallID: 1, clientID: "c-me" }), + ]), + ); + const first = game.myPlayer(); + game.update( + withPlayers(2, [ + makePlayerUpdate({ id: "me", smallID: 1, clientID: "c-me" }), + ]), + ); + expect(game.myPlayer()).toBe(first); + }); + + it("local player's name is overridden with myUsername to bypass censorship", () => { + const game = makeGameView({ + myClientID: "c-me", + myUsername: "RealName", + }); + game.update( + withPlayers(1, [ + makePlayerUpdate({ + id: "me", + smallID: 1, + clientID: "c-me", + name: "ServerName", + displayName: "ServerName", + }), + ]), + ); + expect(game.myPlayer()?.name()).toBe("RealName"); + }); +}); + +describe("GameView.update — units", () => { + it("creates a UnitView on first sighting and reuses it after", () => { + const game = makeGameView(); + const gu1 = makeEmptyGu(1); + gu1.updates[GameUpdateType.Unit] = [makeUnitUpdate({ id: 42, pos: 0 })]; + game.update(gu1); + const first = game.unit(42); + expect(first).toBeDefined(); + + const gu2 = makeEmptyGu(2); + gu2.updates[GameUpdateType.Unit] = [makeUnitUpdate({ id: 42, pos: 1 })]; + game.update(gu2); + expect(game.unit(42)).toBe(first); // same instance + expect(game.unit(42)?.tile()).toBe(1); + }); + + it("units() filters by type and returns only active units", () => { + const game = makeGameView(); + const gu = makeEmptyGu(1); + gu.updates[GameUpdateType.Unit] = [ + makeUnitUpdate({ id: 1, unitType: UnitType.City, isActive: true }), + makeUnitUpdate({ id: 2, unitType: UnitType.Port, isActive: true }), + makeUnitUpdate({ id: 3, unitType: UnitType.City, isActive: false }), + ]; + game.update(gu); + + expect( + game + .units() + .map((u) => u.id()) + .sort(), + ).toEqual([1, 2]); + expect(game.units(UnitType.City).map((u) => u.id())).toEqual([1]); + // The inactive one is still present until the NEXT tick sweeps it. + expect(game.unit(3)).toBeDefined(); + }); + + it("inactive units are deleted on the following tick", () => { + const game = makeGameView(); + + const gu1 = makeEmptyGu(1); + gu1.updates[GameUpdateType.Unit] = [ + makeUnitUpdate({ id: 7, isActive: true }), + ]; + game.update(gu1); + expect(game.unit(7)).toBeDefined(); + + const gu2 = makeEmptyGu(2); + gu2.updates[GameUpdateType.Unit] = [ + makeUnitUpdate({ id: 7, isActive: false }), + ]; + game.update(gu2); + // Still present on the tick they died (renderer can see deadUnit FX). + expect(game.unit(7)).toBeDefined(); + + const gu3 = makeEmptyGu(3); + game.update(gu3); + // Swept on the next tick. + expect(game.unit(7)).toBeUndefined(); + }); + + it("_wasUpdated resets to false at start of tick, then flips back on update", () => { + const game = makeGameView(); + + const gu1 = makeEmptyGu(1); + gu1.updates[GameUpdateType.Unit] = [makeUnitUpdate({ id: 5 })]; + game.update(gu1); + expect(game.unit(5)?.wasUpdated()).toBe(true); + + // Next tick — unit not in updates → wasUpdated should be false + game.update(makeEmptyGu(2)); + expect(game.unit(5)?.wasUpdated()).toBe(false); + + // Next tick — unit reappears → wasUpdated true again + const gu3 = makeEmptyGu(3); + gu3.updates[GameUpdateType.Unit] = [makeUnitUpdate({ id: 5 })]; + game.update(gu3); + expect(game.unit(5)?.wasUpdated()).toBe(true); + }); +}); + +describe("GameView.update — tile deltas", () => { + it("recentlyUpdatedTiles() reflects refs in packedTileUpdates", () => { + const game = makeGameView({ width: 4, height: 4 }); + const gu = makeEmptyGu(1); + // packedTileUpdates is [tileRef, packedState, tileRef, packedState, ...] + // packed state = (terrainByte << 16) | state — use 0 for both to keep tile + // terrain-stable; we're just exercising the delta accumulator. + gu.packedTileUpdates = new Uint32Array([2, 0, 5, 0, 9, 0]); + game.update(gu); + expect(game.recentlyUpdatedTiles().sort((a, b) => a - b)).toEqual([ + 2, 5, 9, + ]); + }); + + it("recentlyUpdatedTerrainTiles() only includes refs where terrain bytes changed", () => { + const game = makeGameView({ width: 4, height: 4 }); + // Tile 3 starts with terrain byte 0. Pack a new terrain byte (0x80 = land) + // for tile 3, and an unchanged terrain (0) for tile 7. + const gu = makeEmptyGu(1); + const TILE_3_PACKED = (0x80 << 16) | 0; // terrain changed + const TILE_7_PACKED = 0; // terrain unchanged + gu.packedTileUpdates = new Uint32Array([ + 3, + TILE_3_PACKED, + 7, + TILE_7_PACKED, + ]); + game.update(gu); + expect(game.recentlyUpdatedTiles().sort((a, b) => a - b)).toEqual([3, 7]); + expect(game.recentlyUpdatedTerrainTiles()).toEqual([3]); + }); + + it("resets deltas to empty arrays each tick", () => { + const game = makeGameView({ width: 4, height: 4 }); + const gu1 = makeEmptyGu(1); + gu1.packedTileUpdates = new Uint32Array([1, 0]); + game.update(gu1); + expect(game.recentlyUpdatedTiles().length).toBe(1); + + // Empty next tick → empty deltas + game.update(makeEmptyGu(2)); + expect(game.recentlyUpdatedTiles()).toEqual([]); + expect(game.recentlyUpdatedTerrainTiles()).toEqual([]); + }); +}); + +describe("GameView.update — tick & lifecycle", () => { + it("ticks() reflects the last update's tick", () => { + const game = makeGameView(); + expect(game.ticks()).toBe(0); // before any update + game.update(makeEmptyGu(42)); + expect(game.ticks()).toBe(42); + game.update(makeEmptyGu(43)); + expect(game.ticks()).toBe(43); + }); + + it("inSpawnPhase() is true until a SpawnPhaseEnd update flips it off", () => { + const game = makeGameView(); + expect(game.inSpawnPhase()).toBe(true); + game.update(makeEmptyGu(5)); + expect(game.inSpawnPhase()).toBe(true); + + const gu = makeEmptyGu(10); + gu.updates[GameUpdateType.SpawnPhaseEnd] = [ + { type: GameUpdateType.SpawnPhaseEnd, startTick: 10 } as ReturnType< + typeof makeEmptyGu + >["updates"][typeof GameUpdateType.SpawnPhaseEnd][number], + ]; + game.update(gu); + expect(game.inSpawnPhase()).toBe(false); + }); + + it("ticksSinceStart returns 0 during spawn phase, otherwise difference from startTick", () => { + const game = makeGameView(); + expect(game.ticksSinceStart()).toBe(0); // spawn phase + + const gu1 = makeEmptyGu(10); + gu1.updates[GameUpdateType.SpawnPhaseEnd] = [ + { type: GameUpdateType.SpawnPhaseEnd, startTick: 10 } as ReturnType< + typeof makeEmptyGu + >["updates"][typeof GameUpdateType.SpawnPhaseEnd][number], + ]; + game.update(gu1); + expect(game.ticksSinceStart()).toBe(0); // tick=10, start=10 + + game.update(makeEmptyGu(15)); + expect(game.ticksSinceStart()).toBe(5); + }); +}); + +describe("GameView — accessors used by FrameBuilder", () => { + it("width() / height() forward to the underlying map", () => { + const game = makeGameView({ width: 12, height: 8 }); + expect(game.width()).toBe(12); + expect(game.height()).toBe(8); + }); + + it("tileStateBuffer() returns a Uint16Array of width*height", () => { + const game = makeGameView({ width: 5, height: 4 }); + const buf = game.tileStateBuffer(); + expect(buf).toBeInstanceOf(Uint16Array); + expect(buf.length).toBe(20); + }); + + it("tileStateBuffer() is a live reference — mutated by update()", () => { + const game = makeGameView({ width: 4, height: 4 }); + const buf = game.tileStateBuffer(); + const gu = makeEmptyGu(1); + // Pack an owner ID into the low 12 bits of state for tile 6. + gu.packedTileUpdates = new Uint32Array([6, 0x123]); + game.update(gu); + expect(buf[6] & 0xfff).toBe(0x123); + }); + + it("player(id) throws for unknown players (matches FrameBuilder's expectation)", () => { + const game = makeGameView(); + expect(() => game.player("unknown")).toThrow(); + }); + + it("config() returns the same Config instance passed in", () => { + const game = makeGameView(); + expect(game.config()).toBe(game.config()); + }); +}); + +describe("GameView.frameData() — renderer contract", () => { + it("returns a stable object reference across ticks", () => { + const game = makeGameView(); + game.update(makeEmptyGu(1)); + const f1 = game.frameData(); + game.update(makeEmptyGu(2)); + const f2 = game.frameData(); + expect(f2).toBe(f1); + }); + + it("frame.tileState is === gameView.tileStateBuffer() (zero-copy)", () => { + const game = makeGameView({ width: 4, height: 4 }); + game.update(makeEmptyGu(1)); + expect(game.frameData().tileState).toBe(game.tileStateBuffer()); + }); + + it("frame.changedTiles is null on the first populate (signals full upload)", () => { + const game = makeGameView({ width: 4, height: 4 }); + const gu1 = makeEmptyGu(1); + gu1.packedTileUpdates = new Uint32Array([1, 0, 2, 0]); + game.update(gu1); + expect(game.frameData().changedTiles).toBeNull(); + }); + + it("frame.changedTiles becomes a delta array on subsequent populates", () => { + const game = makeGameView({ width: 4, height: 4 }); + game.update(makeEmptyGu(1)); + + const gu2 = makeEmptyGu(2); + gu2.packedTileUpdates = new Uint32Array([3, 0, 5, 0, 9, 0]); + game.update(gu2); + const ct = game.frameData().changedTiles; + expect(ct).not.toBeNull(); + expect(ct!.map((t) => t.ref).sort((a, b) => a - b)).toEqual([3, 5, 9]); + }); + + it("changedTiles scratch array is reused across ticks (no per-tick alloc)", () => { + const game = makeGameView({ width: 4, height: 4 }); + game.update(makeEmptyGu(1)); // first populate (changedTiles = null) + const gu2 = makeEmptyGu(2); + gu2.packedTileUpdates = new Uint32Array([1, 0]); + game.update(gu2); + const ct1 = game.frameData().changedTiles; + + const gu3 = makeEmptyGu(3); + gu3.packedTileUpdates = new Uint32Array([2, 0]); + game.update(gu3); + const ct2 = game.frameData().changedTiles; + + expect(ct2).toBe(ct1); // same array instance + }); + + it("frame.units is === gameView.unitStates() (same long-lived map)", () => { + const game = makeGameView(); + game.update(makeEmptyGu(1)); + expect(game.frameData().units).toBe(game.unitStates()); + }); + + it("frame.players is === gameView.playerStates() (same long-lived map)", () => { + const game = makeGameView(); + game.update(makeEmptyGu(1)); + expect(game.frameData().players).toBe(game.playerStates()); + }); + + it("frame.tick reflects the most recent gu.tick", () => { + const game = makeGameView(); + game.update(makeEmptyGu(42)); + expect(game.frameData().tick).toBe(42); + game.update(makeEmptyGu(43)); + expect(game.frameData().tick).toBe(43); + }); + + it("frame.events.deadUnits is populated from inactive Unit updates", () => { + const game = makeGameView(); + const gu = makeEmptyGu(1); + gu.updates[GameUpdateType.Unit] = [ + makeUnitUpdate({ id: 1, isActive: true, pos: 10 }), + makeUnitUpdate({ id: 2, isActive: false, pos: 20 }), + makeUnitUpdate({ id: 3, isActive: false, pos: 30 }), + ]; + game.update(gu); + const dead = game.frameData().events.deadUnits; + expect(dead.length).toBe(2); + expect(dead.map((d) => d.pos).sort((a, b) => a - b)).toEqual([20, 30]); + }); + + it("frame.events arrays are cleared each tick (no event leakage)", () => { + const game = makeGameView(); + const gu1 = makeEmptyGu(1); + gu1.updates[GameUpdateType.Unit] = [ + makeUnitUpdate({ id: 1, isActive: false }), + ]; + game.update(gu1); + expect(game.frameData().events.deadUnits.length).toBe(1); + + // Empty next tick → events cleared + game.update(makeEmptyGu(2)); + expect(game.frameData().events.deadUnits.length).toBe(0); + }); + + it("frame.events.deadUnits array is reused (same reference)", () => { + const game = makeGameView(); + game.update(makeEmptyGu(1)); + const a1 = game.frameData().events.deadUnits; + game.update(makeEmptyGu(2)); + expect(game.frameData().events.deadUnits).toBe(a1); + }); + + it("frame.tileMode is 'live'", () => { + const game = makeGameView(); + expect(game.frameData().tileMode).toBe("live"); + }); + + it("frame.structuresDirty is true on first populate (force initial upload)", () => { + const game = makeGameView(); + game.update(makeEmptyGu(1)); + expect(game.frameData().structuresDirty).toBe(true); + }); + + it("frame.structuresDirty resets between ticks when no structure changes", () => { + const game = makeGameView(); + game.update(makeEmptyGu(1)); + game.update(makeEmptyGu(2)); + expect(game.frameData().structuresDirty).toBe(false); + }); +}); diff --git a/tests/client/view/PlayerView.test.ts b/tests/client/view/PlayerView.test.ts new file mode 100644 index 0000000000..4acea7a618 --- /dev/null +++ b/tests/client/view/PlayerView.test.ts @@ -0,0 +1,285 @@ +/** + * PlayerView is a thin accessor wrapping a PlayerUpdate record plus precomputed + * colors. Tests verify each accessor forwards the underlying data, that the + * color variants (neutral/friendly/embargo) are precomputed at construction, + * and that relation predicates (allied / same-team / friendly / embargo) match + * what the FrameBuilder relies on when populating PlayerState. + */ + +import { describe, expect, it } from "vitest"; +import { PlayerView } from "../../../src/client/view/PlayerView"; +import { PlayerType } from "../../../src/core/game/Game"; +import { GameUpdateType } from "../../../src/core/game/GameUpdates"; +import { + makeEmptyGu, + makeGameView, + makeNameViewData, + makePlayerUpdate, + makePlayerView, +} from "../../util/viewStubs"; + +describe("PlayerView accessors", () => { + it("forwards data fields", () => { + const p = makePlayerView({ + data: { + id: "player-a", + smallID: 7, + clientID: "client-a", + name: "Alice", + displayName: "Alice", + playerType: PlayerType.Human, + isAlive: true, + isDisconnected: false, + isLobbyCreator: true, + tilesOwned: 42, + gold: 999n, + troops: 250, + }, + }); + + expect(p.id()).toBe("player-a"); + expect(p.smallID()).toBe(7); + expect(p.clientID()).toBe("client-a"); + expect(p.name()).toBe("Alice"); + expect(p.displayName()).toBe("Alice"); + expect(p.type()).toBe(PlayerType.Human); + expect(p.isAlive()).toBe(true); + expect(p.isDisconnected()).toBe(false); + expect(p.isLobbyCreator()).toBe(true); + expect(p.numTilesOwned()).toBe(42); + expect(p.gold()).toBe(999n); + expect(p.troops()).toBe(250); + }); + + it("isPlayer() is always true", () => { + expect(makePlayerView().isPlayer()).toBe(true); + }); + + it("team() returns null when team is undefined on data", () => { + expect(makePlayerView({ data: { team: undefined } }).team()).toBeNull(); + }); + + it("team() forwards a set team", () => { + expect(makePlayerView({ data: { team: "red" } }).team()).toBe("red"); + }); + + it("isTraitor + getTraitorRemainingTicks forward, with min clamp at 0", () => { + const traitor = makePlayerView({ + data: { isTraitor: true, traitorRemainingTicks: 5 }, + }); + expect(traitor.isTraitor()).toBe(true); + expect(traitor.getTraitorRemainingTicks()).toBe(5); + + // Negative or missing → clamped to 0 + const expired = makePlayerView({ + data: { isTraitor: false, traitorRemainingTicks: -3 }, + }); + expect(expired.getTraitorRemainingTicks()).toBe(0); + + const missing = makePlayerView({ data: { isTraitor: false } }); + expect(missing.getTraitorRemainingTicks()).toBe(0); + }); + + it("nameLocation() returns nameData passed at construction", () => { + const nameData = makeNameViewData({ x: 12, y: 34, size: 20 }); + expect(makePlayerView({ nameData }).nameLocation()).toBe(nameData); + }); + + it("outgoingEmojis / outgoingAttacks / incomingAttacks / alliances forward arrays", () => { + const alliance = { + id: 1, + other: { id: "ally", smallID: 2 }, + createdAt: 0, + expiresAt: 100, + onlyOneAgreedToExtend: false, + } as unknown as ReturnType[number]; + const attack = { + attackerID: 1, + targetID: 0, + troops: 50, + id: "attack-a", + retreating: false, + } as unknown as ReturnType[number]; + const emoji = { + message: 0, + senderID: 1, + recipientID: 2, + createdAt: 0, + } as unknown as ReturnType[number]; + + const p = makePlayerView({ + data: { + alliances: [alliance], + outgoingAttacks: [attack], + incomingAttacks: [], + outgoingEmojis: [emoji], + }, + }); + + expect(p.alliances()).toEqual([alliance]); + expect(p.outgoingAttacks()).toEqual([attack]); + expect(p.incomingAttacks()).toEqual([]); + expect(p.outgoingEmojis()).toEqual([emoji]); + }); +}); + +describe("PlayerView colors", () => { + it("territoryColor() with no tile returns a Colord", () => { + const c = makePlayerView().territoryColor(); + expect(typeof c.toHex()).toBe("string"); + }); + + it("structureColors() returns precomputed light/dark", () => { + const colors = makePlayerView().structureColors(); + expect(colors).toHaveProperty("light"); + expect(colors).toHaveProperty("dark"); + }); + + it("borderColor() with no tile returns the base border color", () => { + const p = makePlayerView(); + const noTile = p.borderColor(); + // Same value should come back for repeat calls (cached). + expect(p.borderColor().toHex()).toBe(noTile.toHex()); + }); +}); + +describe("PlayerView relations", () => { + function pair( + aSmall: number, + bSmall: number, + opts: { + aAllies?: number[]; + aTeam?: string; + bTeam?: string; + // Embargoes are renderer-format: stringified smallIDs of the OTHER player. + aEmbargoSmallIDs?: string[]; + bEmbargoSmallIDs?: string[]; + aOutgoingReq?: string[]; + } = {}, + ) { + const a = makePlayerView({ + data: { + id: "a", + smallID: aSmall, + allies: opts.aAllies ?? [], + team: opts.aTeam, + outgoingAllianceRequests: opts.aOutgoingReq ?? [], + }, + }); + const b = makePlayerView({ + data: { + id: "b", + smallID: bSmall, + team: opts.bTeam, + }, + }); + if (opts.aEmbargoSmallIDs) a.setEmbargoSmallIDs(opts.aEmbargoSmallIDs); + if (opts.bEmbargoSmallIDs) b.setEmbargoSmallIDs(opts.bEmbargoSmallIDs); + return { a, b }; + } + + it("isAlliedWith() reflects ally smallIDs in data.allies", () => { + const { a, b } = pair(1, 2, { aAllies: [2] }); + expect(a.isAlliedWith(b)).toBe(true); + expect(b.isAlliedWith(a)).toBe(false); // b has no allies set + }); + + it("isOnSameTeam() compares data.team and treats undefined as no team", () => { + const same = pair(1, 2, { aTeam: "red", bTeam: "red" }); + const diff = pair(1, 2, { aTeam: "red", bTeam: "blue" }); + const noTeam = pair(1, 2); + expect(same.a.isOnSameTeam(same.b)).toBe(true); + expect(diff.a.isOnSameTeam(diff.b)).toBe(false); + // Two players with no team set should NOT count as same team. + expect(noTeam.a.isOnSameTeam(noTeam.b)).toBe(false); + }); + + it("isFriendly() = allied OR same team", () => { + const allied = pair(1, 2, { aAllies: [2] }); + expect(allied.a.isFriendly(allied.b)).toBe(true); + + const teammates = pair(1, 2, { aTeam: "red", bTeam: "red" }); + expect(teammates.a.isFriendly(teammates.b)).toBe(true); + + const strangers = pair(1, 2); + expect(strangers.a.isFriendly(strangers.b)).toBe(false); + }); + + it("hasEmbargoAgainst / hasEmbargo are symmetric on the second", () => { + // a embargoes b — by smallID (renderer format) + const aEmbargoesB = pair(1, 2, { aEmbargoSmallIDs: ["2"] }); + // One-way directional embargo from a + expect(aEmbargoesB.a.hasEmbargoAgainst(aEmbargoesB.b)).toBe(true); + expect(aEmbargoesB.b.hasEmbargoAgainst(aEmbargoesB.a)).toBe(false); + // Symmetric version is true from either side + expect(aEmbargoesB.a.hasEmbargo(aEmbargoesB.b)).toBe(true); + expect(aEmbargoesB.b.hasEmbargo(aEmbargoesB.a)).toBe(true); + }); + + it("isRequestingAllianceWith() reflects outgoingAllianceRequests", () => { + const { a, b } = pair(1, 2, { aOutgoingReq: ["b"] }); + expect(a.isRequestingAllianceWith(b)).toBe(true); + expect(b.isRequestingAllianceWith(a)).toBe(false); + }); +}); + +describe("PlayerView in a GameView context", () => { + it("allies() resolves smallIDs through the game's smallID → PlayerView map", () => { + // Build a GameView and feed it two players so allies() can resolve. + const game = makeGameView(); + const aliceUpdate = makePlayerUpdate({ + id: "alice", + smallID: 1, + clientID: "c-alice", + name: "Alice", + allies: [2], + }); + const bobUpdate = makePlayerUpdate({ + id: "bob", + smallID: 2, + clientID: "c-bob", + name: "Bob", + }); + + // Drive a tick through the GameView so it creates the PlayerViews and + // registers smallID lookups — that's the path FrameBuilder & PlayerView use. + const gu = makeEmptyGu(1); + gu.updates[GameUpdateType.Player] = [aliceUpdate, bobUpdate]; + gu.playerNameViewData = { + alice: makeNameViewData(), + bob: makeNameViewData(), + }; + game.update(gu); + + const alice = game.player("alice"); + const bob = game.player("bob"); + expect(alice.allies()).toEqual([bob]); + }); + + it("isMe() is true only for the player matching myClientID", () => { + const game = makeGameView({ myClientID: "c-me" }); + const me = makePlayerUpdate({ + id: "me", + smallID: 1, + clientID: "c-me", + name: "Me", + }); + const other = makePlayerUpdate({ + id: "other", + smallID: 2, + clientID: "c-other", + name: "Other", + }); + + const gu = makeEmptyGu(1); + gu.updates[GameUpdateType.Player] = [me, other]; + gu.playerNameViewData = { + me: makeNameViewData(), + other: makeNameViewData(), + }; + game.update(gu); + + expect(game.player("me").isMe()).toBe(true); + expect(game.player("other").isMe()).toBe(false); + }); +}); diff --git a/tests/client/view/UnitView.test.ts b/tests/client/view/UnitView.test.ts new file mode 100644 index 0000000000..e5b8bf98c8 --- /dev/null +++ b/tests/client/view/UnitView.test.ts @@ -0,0 +1,258 @@ +/** + * UnitView is mostly a thin accessor over a UnitUpdate record. Tests verify + * each accessor returns the underlying data, that update() swaps the backing + * record, that lastPos tracking works as the simulation advances units, and + * that the trickier missile-readiness math is correct. + */ + +import { describe, expect, it } from "vitest"; +import { UnitView } from "../../../src/client/view/UnitView"; +import { + TrainType, + TransportShipState, + UnitType, + WarshipState, +} from "../../../src/core/game/Game"; +import { makeGameView, makeUnitUpdate, stubConfig } from "../../util/viewStubs"; + +describe("UnitView accessors", () => { + it("forwards data fields", () => { + const game = makeGameView(); + const u = new UnitView( + game, + makeUnitUpdate({ + id: 42, + unitType: UnitType.City, + ownerID: 7, + pos: 100, + lastPos: 99, + troops: 250, + level: 3, + hasTrainStation: true, + targetable: false, + markedForDeletion: false, + isActive: true, + reachedTarget: false, + }), + ); + + expect(u.id()).toBe(42); + expect(u.type()).toBe(UnitType.City); + expect(u.troops()).toBe(250); + expect(u.level()).toBe(3); + expect(u.hasTrainStation()).toBe(true); + expect(u.targetable()).toBe(false); + expect(u.markedForDeletion()).toBe(false); + expect(u.isActive()).toBe(true); + expect(u.reachedTarget()).toBe(false); + expect(u.tile()).toBe(100); + }); + + it("tracks createdAt from the GameView's tick at construction", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate()); + expect(u.createdAt()).toBe(0); // GameView.ticks() returns 0 before any update + }); + + it("returns the latest data after update()", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate({ troops: 100, pos: 1 })); + u.update(makeUnitUpdate({ troops: 250, pos: 5 })); + expect(u.troops()).toBe(250); + expect(u.tile()).toBe(5); + }); + + it("update() pushes new pos into lastPos", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate({ pos: 1 })); + expect(u.lastTile()).toBe(1); + u.update(makeUnitUpdate({ pos: 2 })); + expect(u.lastTiles()).toEqual([1, 2]); + u.update(makeUnitUpdate({ pos: 3 })); + expect(u.lastTiles()).toEqual([1, 2, 3]); + }); + + it("lastTile() returns the first remembered pos", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate({ pos: 1 })); + u.update(makeUnitUpdate({ pos: 2 })); + u.update(makeUnitUpdate({ pos: 3 })); + expect(u.lastTile()).toBe(1); + }); + + it("applyDerivedPosition pushes a new pos and shifts lastPos in data", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate({ pos: 10, lastPos: 9 })); + u.applyDerivedPosition(11); + expect(u.tile()).toBe(11); + expect(u.lastTiles()).toEqual([10, 11]); + }); + + it("hasHealth() reflects whether health is set", () => { + const game = makeGameView(); + expect(new UnitView(game, makeUnitUpdate({ health: 50 })).hasHealth()).toBe( + true, + ); + expect(new UnitView(game, makeUnitUpdate()).hasHealth()).toBe(false); + }); + + it("health() returns 0 when unset", () => { + const game = makeGameView(); + expect(new UnitView(game, makeUnitUpdate()).health()).toBe(0); + expect(new UnitView(game, makeUnitUpdate({ health: 42 })).health()).toBe( + 42, + ); + }); + + it("isUnderConstruction reflects the explicit boolean", () => { + const game = makeGameView(); + expect( + new UnitView( + game, + makeUnitUpdate({ underConstruction: true }), + ).isUnderConstruction(), + ).toBe(true); + expect( + new UnitView( + game, + makeUnitUpdate({ underConstruction: false }), + ).isUnderConstruction(), + ).toBe(false); + // Undefined is treated as false (not under construction). + expect(new UnitView(game, makeUnitUpdate()).isUnderConstruction()).toBe( + false, + ); + }); + + it("trainType() / isLoaded() forward optional train fields", () => { + const game = makeGameView(); + const u = new UnitView( + game, + makeUnitUpdate({ trainType: TrainType.Engine, loaded: true }), + ); + expect(u.trainType()).toBe(TrainType.Engine); + expect(u.isLoaded()).toBe(true); + }); + + it("transportShipState() returns a default when missing", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate()); + expect(u.transportShipState()).toEqual({ isRetreating: false, troops: 0 }); + }); + + it("transportShipState() forwards when set", () => { + const game = makeGameView(); + const state: TransportShipState = { isRetreating: true, troops: 50 }; + const u = new UnitView(game, makeUnitUpdate({ transportShipState: state })); + expect(u.transportShipState()).toBe(state); + }); + + it("warshipState() throws when not a warship state", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate()); + expect(() => u.warshipState()).toThrow(); + }); + + it("warshipState() forwards when present", () => { + const game = makeGameView(); + const state: WarshipState = { + isInCombat: false, + patrolTile: 0, + lastAttackTile: 0, + bossUnitId: null, + } as unknown as WarshipState; + const u = new UnitView(game, makeUnitUpdate({ warshipState: state })); + expect(u.warshipState()).toBe(state); + }); + + it("isInCombat() reflects warshipState.isInCombat (or false if missing)", () => { + const game = makeGameView(); + expect(new UnitView(game, makeUnitUpdate()).isInCombat()).toBe(false); + const combat = new UnitView( + game, + makeUnitUpdate({ + warshipState: { isInCombat: true } as unknown as WarshipState, + }), + ); + expect(combat.isInCombat()).toBe(true); + }); + + it("targetUnitId / targetTile pass through", () => { + const game = makeGameView(); + const u = new UnitView( + game, + makeUnitUpdate({ targetUnitId: 99, targetTile: 12 }), + ); + expect(u.targetUnitId()).toBe(99); + expect(u.targetTile()).toBe(12); + }); + + it("missileTimerQueue() forwards the array", () => { + const game = makeGameView(); + const u = new UnitView( + game, + makeUnitUpdate({ missileTimerQueue: [10, 20, 30] }), + ); + expect(u.missileTimerQueue()).toEqual([10, 20, 30]); + }); + + it("touch / updateWarshipState / updateTransportShipState throw on view", () => { + const game = makeGameView(); + const u = new UnitView(game, makeUnitUpdate()); + expect(() => u.touch()).toThrow(); + expect(() => u.updateWarshipState({})).toThrow(); + expect(() => u.updateTransportShipState({ isRetreating: false })).toThrow(); + }); + + describe("missileReadinesss", () => { + it("returns 1 when nothing is reloading", () => { + const game = makeGameView(); + const u = new UnitView( + game, + makeUnitUpdate({ level: 3, missileTimerQueue: [] }), + ); + expect(u.missileReadinesss()).toBe(1); + }); + + it("returns 0 when all missiles are reloading and level > 1", () => { + const game = makeGameView({ config: stubConfig() }); + const u = new UnitView( + game, + makeUnitUpdate({ + unitType: UnitType.SAMLauncher, + level: 2, + missileTimerQueue: [0, 0], // both reloading, started at tick 0 + }), + ); + // Just-launched: progress is 0, readiness 0/2. + expect(u.missileReadinesss()).toBe(0); + }); + + it("returns partial readiness when missiles are partway through cooldown", () => { + // SAMCooldown = 120 in stub. Half-way at tick 60. Level 2 with both reloading + // means readiness = 0/2 from ready missiles + 2 * (60/120) / 2 = 0.5. + // But game.ticks() returns 0 with no update. So progress = 0 - 0 = 0 → 0. + // Use a game with a tick number injected. + const config = stubConfig({ + SAMCooldown: () => 120, + SiloCooldown: () => 75, + } as unknown as Partial< + typeof stubConfig extends () => infer C ? C : never + >); + const game = makeGameView({ config }); + const u = new UnitView( + game, + makeUnitUpdate({ + unitType: UnitType.SAMLauncher, + level: 2, + missileTimerQueue: [0, 0], + }), + ); + // Without advancing game ticks, readiness = (2-2)/2 + 2*((0-0)/120)/2 = 0. + // We can't easily advance ticks without going through update(); just assert <=1. + const r = u.missileReadinesss(); + expect(r).toBeGreaterThanOrEqual(0); + expect(r).toBeLessThanOrEqual(1); + }); + }); +}); diff --git a/tests/core/game/GameMap.tileStateBuffer.test.ts b/tests/core/game/GameMap.tileStateBuffer.test.ts new file mode 100644 index 0000000000..5da53f05ab --- /dev/null +++ b/tests/core/game/GameMap.tileStateBuffer.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { GameMapImpl } from "../../../src/core/game/GameMap"; + +describe("GameMap.tileStateBuffer", () => { + it("returns a Uint16Array sized to width * height", () => { + const map = new GameMapImpl(10, 8, new Uint8Array(10 * 8), 0); + const buf = map.tileStateBuffer(); + expect(buf).toBeInstanceOf(Uint16Array); + expect(buf.length).toBe(80); + }); + + it("returns a live reference — updateTile() mutates the same buffer", () => { + const map = new GameMapImpl(4, 4, new Uint8Array(16), 0); + const buf = map.tileStateBuffer(); + // Writes go through updateTile (packed uint32: high 16 bits = terrain byte, low 16 = state). + map.updateTile(5, 0x00abcd); + expect(buf[5]).toBe(0xabcd); + }); + + it("returns the same array on every call (zero-copy)", () => { + const map = new GameMapImpl(4, 4, new Uint8Array(16), 0); + expect(map.tileStateBuffer()).toBe(map.tileStateBuffer()); + }); + + it("reflects ownerID writes in the low 12 bits of each cell", () => { + const map = new GameMapImpl(4, 4, new Uint8Array(16), 0); + map.setOwnerID(7, 0x123); + expect(map.tileStateBuffer()[7] & 0xfff).toBe(0x123); + }); +}); diff --git a/tests/util/viewStubs.ts b/tests/util/viewStubs.ts new file mode 100644 index 0000000000..d02bccced9 --- /dev/null +++ b/tests/util/viewStubs.ts @@ -0,0 +1,224 @@ +/** + * Stub builders for GameView/PlayerView/UnitView unit tests. + * + * These tests don't go through the full game setup (which creates a worker + * and runs the simulation) — they exercise the view classes directly with + * minimal stubs for their dependencies. + */ + +import { colord } from "colord"; +import { GameView } from "../../src/client/view/GameView"; +import { PlayerView } from "../../src/client/view/PlayerView"; +import { Config } from "../../src/core/configuration/Config"; +import { Theme } from "../../src/core/configuration/Theme"; +import { + NameViewData, + PlayerType, + Team, + UnitType, +} from "../../src/core/game/Game"; +import { GameMapImpl } from "../../src/core/game/GameMap"; +import { + GameUpdateType, + GameUpdateViewData, + PlayerUpdate, + UnitUpdate, +} from "../../src/core/game/GameUpdates"; +import { TerrainMapData } from "../../src/core/game/TerrainMapLoader"; +import { Player, PlayerCosmetics } from "../../src/core/Schemas"; +import { WorkerClient } from "../../src/core/worker/WorkerClient"; + +/** Theme stub — returns deterministic colors so PlayerView's color math works. */ +export function stubTheme(): Theme { + const white = colord("#ffffff"); + const grey = colord("#808080"); + const defended = { light: white, dark: grey }; + return { + teamColor: () => white, + territoryColor: () => white, + structureColors: () => defended, + borderColor: () => grey, + defendedBorderColors: () => defended, + focusedBorderColor: () => grey, + terrainColor: () => white, + backgroundColor: () => white, + falloutColor: () => white, + font: () => "Arial", + textColor: () => "#000000", + selfColor: () => white, + allyColor: () => white, + neutralColor: () => grey, + enemyColor: () => grey, + spawnHighlightColor: () => white, + spawnHighlightSelfColor: () => white, + spawnHighlightTeamColor: () => white, + spawnHighlightEnemyColor: () => white, + }; +} + +/** Minimum Config stub for view tests. Extend as test needs grow. */ +export function stubConfig(overrides: Partial = {}): Config { + const theme = stubTheme(); + const cfg = { + theme: () => theme, + SAMCooldown: () => 120, + SiloCooldown: () => 75, + deleteUnitCooldown: () => 0, + spawnImmunityDuration: () => 0, + nationSpawnImmunityDuration: () => 0, + unitInfo: () => ({ maxHealth: 100, constructionDuration: 20 }), + disableAlliances: () => false, + allianceDuration: () => 100, + deletionMarkDuration: () => 300, + nukeMagnitudes: () => ({ inner: 0, outer: 0 }), + nukeAllianceBreakThreshold: () => 0, + userSettings: () => ({}), + ...overrides, + } as unknown as Config; + return cfg; +} + +/** WorkerClient stub. View classes only call worker.* in async methods we don't exercise. */ +export function stubWorker(): WorkerClient { + return {} as unknown as WorkerClient; +} + +/** Build TerrainMapData wrapping a fresh GameMapImpl of the given size. */ +export function stubTerrainMap(width = 10, height = 10): TerrainMapData { + const terrain = new Uint8Array(width * height); + const gameMap = new GameMapImpl(width, height, terrain, 0); + return { + nations: [], + additionalNations: [], + gameMap, + miniGameMap: gameMap, + } as unknown as TerrainMapData; +} + +export interface GameViewStubOptions { + width?: number; + height?: number; + myClientID?: string; + myUsername?: string; + myClanTag?: string | null; + humans?: Player[]; + config?: Config; +} + +/** Construct a GameView with minimal dependencies. */ +export function makeGameView(opts: GameViewStubOptions = {}): GameView { + return new GameView( + stubWorker(), + opts.config ?? stubConfig(), + stubTerrainMap(opts.width ?? 10, opts.height ?? 10), + opts.myClientID, + opts.myUsername ?? "tester", + opts.myClanTag ?? null, + "test-game", + opts.humans ?? [], + ); +} + +// ── Synthetic update builders ── + +export function makePlayerUpdate( + overrides: Partial = {}, +): PlayerUpdate { + return { + type: GameUpdateType.Player, + clientID: "client-a", + name: "Alice", + displayName: "Alice", + id: "player-a", + smallID: 1, + playerType: PlayerType.Human, + isAlive: true, + isDisconnected: false, + tilesOwned: 0, + gold: 0n, + troops: 100, + allies: [], + embargoes: new Set(), + isTraitor: false, + targets: [], + outgoingEmojis: [], + outgoingAttacks: [], + incomingAttacks: [], + outgoingAllianceRequests: [], + alliances: [], + hasSpawned: true, + betrayals: 0, + lastDeleteUnitTick: 0, + isLobbyCreator: false, + ...overrides, + }; +} + +export function makeUnitUpdate( + overrides: Partial = {}, +): UnitUpdate { + return { + type: GameUpdateType.Unit, + unitType: UnitType.Warship, + troops: 0, + id: 1, + ownerID: 1, + pos: 0, + lastPos: 0, + isActive: true, + reachedTarget: false, + targetable: true, + markedForDeletion: false, + missileTimerQueue: [], + level: 1, + hasTrainStation: false, + ...overrides, + }; +} + +export function makeNameViewData( + overrides: Partial = {}, +): NameViewData { + return { x: 0, y: 0, size: 12, ...overrides }; +} + +export interface PlayerViewStubOptions { + game?: GameView; + data?: Partial; + nameData?: NameViewData; + cosmetics?: PlayerCosmetics; +} + +/** Construct a PlayerView with minimal dependencies. */ +export function makePlayerView(opts: PlayerViewStubOptions = {}): PlayerView { + return new PlayerView( + opts.game ?? makeGameView(), + makePlayerUpdate(opts.data), + opts.nameData ?? makeNameViewData(), + opts.cosmetics ?? {}, + ); +} + +/** + * Build a GameUpdateViewData with no updates and an empty packed tile delta. + * Caller can fill in updates[GameUpdateType.X] arrays as needed. + */ +export function makeEmptyGu( + tick: number, + overrides: Partial = {}, +): GameUpdateViewData { + const updates = Object.fromEntries( + Object.values(GameUpdateType) + .filter((v): v is number => typeof v === "number") + .map((k) => [k, []]), + ) as unknown as GameUpdateViewData["updates"]; + return { + tick, + updates, + packedTileUpdates: new Uint32Array(0), + playerNameViewData: {}, + ...overrides, + }; +} + +export { Team }; From 3af17511191a4051353d3e389f83c7708001536b Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 16:42:05 -0700 Subject: [PATCH 04/34] fix: eliminate WebGL camera-sync lag and forced-reflow cost MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues with mounting the WebGL renderer alongside canvas2D: 1. One-frame camera lag. WebGL had its own RAF loop independent of canvas2D's. When the user panned, WebGL's RAF could fire before canvas2D's syncCamera ran, drawing with stale camera state. Fix: pass a capturing raf/caf to the renderer so its loop never actually schedules itself; invoke the captured frame callback synchronously from canvas2D's onPreRender hook, after setCameraState. Both renderers now lock-step on a single RAF. 2. Layout thrashing. syncCamera read glCanvas.clientWidth/Height every frame, forcing a synchronous layout flush — ~11% CPU under Layout. Fix: cache canvas dimensions and update via ResizeObserver. Canvas size changes are rare; the cached values are accurate between resizes. --- src/client/ClientGameRunner.ts | 45 +++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index ea734b2501..711fe43d7b 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -248,6 +248,21 @@ function mountWebGLDebugRenderer( glCanvas.style.pointerEvents = "none"; document.body.insertBefore(glCanvas, document.body.firstChild); + // Capture the WebGL renderer's animation-frame callback rather than letting + // it run its own RAF loop. Two independent RAF loops race: when the user + // pans, the WebGL renderer can draw with one-frame-stale camera state + // because its RAF fires before canvas2D's RAF (which would have synced the + // camera). Driving WebGL's draw synchronously from canvas2D's onPreRender + // hook locks them to the same frame. + let cachedWebGLFrameCallback: FrameRequestCallback | null = null; + const captureRaf = (cb: FrameRequestCallback): number => { + cachedWebGLFrameCallback = cb; + return 0; + }; + const captureCaf = (_id: number): void => { + cachedWebGLFrameCallback = null; + }; + const palette = new Float32Array(4096 * 2 * 4); const view = new WebGLGameView( glCanvas, @@ -264,6 +279,8 @@ function mountWebGLDebugRenderer( }, terrainBytes, palette, + captureRaf, + captureCaf, ); // Names are rendered by the existing HTML NameLayer; disable the renderer's @@ -277,20 +294,40 @@ function mountWebGLDebugRenderer( } }); + // Cache canvas dimensions to avoid forced reflows every frame. Reading + // clientWidth/clientHeight flushes pending layout — at 60fps that's a + // measurable cost. Only update on resize events from the observer. + let cachedCanvasW = glCanvas.clientWidth; + let cachedCanvasH = glCanvas.clientHeight; + const resizeObs = new ResizeObserver((entries) => { + for (const entry of entries) { + const { width, height } = entry.contentRect; + if (width > 0 && height > 0) { + cachedCanvasW = width; + cachedCanvasH = height; + } + } + }); + resizeObs.observe(glCanvas); + const syncCamera = (): void => { const scale = transformHandler.scale; const dpr = window.devicePixelRatio || 1; - const canvasW = glCanvas.clientWidth; - const canvasH = glCanvas.clientHeight; const centerX = transformHandler.offsetX + mapWidth / 2 + - (canvasW - mapWidth) / (2 * scale); + (cachedCanvasW - mapWidth) / (2 * scale); const centerY = transformHandler.offsetY + mapHeight / 2 + - (canvasH - mapHeight) / (2 * scale); + (cachedCanvasH - mapHeight) / (2 * scale); view.setCameraState(centerX, centerY, scale * dpr); + // Invoke the WebGL renderer's frame callback synchronously, with the just- + // updated camera state. The callback re-arms itself via captureRaf, so + // we'll get a fresh callback ready for the next canvas2D frame. + const cb = cachedWebGLFrameCallback; + cachedWebGLFrameCallback = null; + cb?.(performance.now()); }; (window as unknown as { __webglView?: unknown }).__webglView = view; From 7b8c950bef324f4d27b358d6af2ace36770433e5 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 16:48:06 -0700 Subject: [PATCH 05/34] fix: remove duplicate conquest gold popup on canvas2D MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DynamicUILayer subscribed to ConquestEvent and drew a floating "+gold" text on capture, but the WebGL renderer now draws the same popup via ConquestPopupPass (fed through applyConquestEvents in uploadFrameData). The result was two popups stacked on every capture. Drop the canvas2D handler. Bonus events and unit-death FX use the same duplication pattern but are left intact for now — separate change when the WebGL versions are verified visually. --- src/client/graphics/layers/DynamicUILayer.ts | 24 +------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/client/graphics/layers/DynamicUILayer.ts b/src/client/graphics/layers/DynamicUILayer.ts index b151a1b832..a6200e360a 100644 --- a/src/client/graphics/layers/DynamicUILayer.ts +++ b/src/client/graphics/layers/DynamicUILayer.ts @@ -1,11 +1,7 @@ import { renderNumber } from "src/client/Utils"; import { EventBus } from "src/core/EventBus"; import { UnitType } from "src/core/game/Game"; -import { - BonusEventUpdate, - ConquestUpdate, - GameUpdateType, -} from "src/core/game/GameUpdates"; +import { BonusEventUpdate, GameUpdateType } from "src/core/game/GameUpdates"; import type { GameView, UnitView } from "../../../core/game/GameView"; import { MoveWarshipIntentEvent } from "../../Transport"; import { TransformHandler } from "../TransformHandler"; @@ -18,7 +14,6 @@ import { Layer } from "./Layer"; const TEXT_OFFSET_Y = -5; const TEXT_STACK_SPACING = 8; -const TEXT_DURATION = 2500; export class DynamicUILayer implements Layer { private readonly uiElements: Array = []; @@ -61,11 +56,6 @@ export class DynamicUILayer implements Layer { if (bonusEvent === undefined) return; this.onBonusEvent(bonusEvent); }); - - updates[GameUpdateType.ConquestEvent]?.forEach((update) => { - if (update === undefined) return; - this.onConquestEvent(update); - }); } onBonusEvent(bonus: BonusEventUpdate) { @@ -89,18 +79,6 @@ export class DynamicUILayer implements Layer { } } - onConquestEvent(conquest: ConquestUpdate) { - // Only display text for the current player - const conqueror = this.game.player(conquest.conquerorId); - if (conqueror !== this.game.myPlayer()) { - return; - } - const nameLocation = this.game.player(conquest.conqueredId).nameLocation(); - const x = nameLocation.x; - const y = nameLocation.y; - this.addNumber(conquest.gold, x, y + 8, TEXT_DURATION, 0); - } - onUnitEvent(unit: UnitView) { switch (unit.type()) { case UnitType.HydrogenBomb: From bb619c2c44cfcf93ccce232bd6541ef16e4c53f9 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 17:22:45 -0700 Subject: [PATCH 06/34] add configurable dot size for zoomed-out structures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Structures collapse to a dot when zoomed out past dotsZoomThreshold. The dot scale was hardcoded to 1.0 / 2.5 (≈0.4) in structure.vert.glsl. Promote it to a render setting (`structure.dotScale`) so it's tunable alongside iconSize / dotsZoomThreshold. Default 0.4 preserves current behavior. Plumbed via uDotScale uniform on StructurePass. --- src/client/render/gl/passes/structure-pass.ts | 3 +++ src/client/render/gl/render-settings.json | 7 ++++--- src/client/render/gl/render-settings.ts | 2 ++ src/client/render/gl/shaders/structure/structure.vert.glsl | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/client/render/gl/passes/structure-pass.ts b/src/client/render/gl/passes/structure-pass.ts index d0f89c258f..ea186f0eae 100644 --- a/src/client/render/gl/passes/structure-pass.ts +++ b/src/client/render/gl/passes/structure-pass.ts @@ -74,6 +74,7 @@ export class StructurePass { private uZoom: WebGLUniformLocation; private uIconSize: WebGLUniformLocation; private uDotsThreshold: WebGLUniformLocation; + private uDotScale: WebGLUniformLocation; private uScaleFactor: WebGLUniformLocation; private uShapeScales: WebGLUniformLocation; private uIconFills: WebGLUniformLocation; @@ -140,6 +141,7 @@ export class StructurePass { this.uCamera = gl.getUniformLocation(this.program, "uCamera")!; this.uZoom = gl.getUniformLocation(this.program, "uZoom")!; this.uIconSize = gl.getUniformLocation(this.program, "uIconSize")!; + this.uDotScale = gl.getUniformLocation(this.program, "uDotScale")!; this.uDotsThreshold = gl.getUniformLocation( this.program, "uDotsThreshold", @@ -330,6 +332,7 @@ export class StructurePass { gl.uniform1f(this.uZoom, zoom); gl.uniform1f(this.uIconSize, ss.iconSize); gl.uniform1f(this.uDotsThreshold, ss.dotsZoomThreshold); + gl.uniform1f(this.uDotScale, ss.dotScale); gl.uniform1f(this.uScaleFactor, ss.iconScaleFactorZoomedOut); // Build per-structure uniform arrays from settings, ordered by atlas column diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index c956c4ce8a..b9da108d47 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -82,9 +82,10 @@ "railAlpha": 1 }, "structure": { - "iconSize": 35, - "dotsZoomThreshold": 0.5, - "iconScaleFactorZoomedOut": 1.4, + "iconSize": 65, + "dotsZoomThreshold": 1.2, + "dotScale": 0.3, + "iconScaleFactorZoomedOut": 3.5, "shapes": { "City": { "scale": 1, diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts index bd67bfea52..adbd373cf0 100644 --- a/src/client/render/gl/render-settings.ts +++ b/src/client/render/gl/render-settings.ts @@ -86,6 +86,8 @@ export interface RenderSettings { structure: { iconSize: number; dotsZoomThreshold: number; + /** Icon size multiplier when zoomed out past dotsZoomThreshold. */ + dotScale: number; iconScaleFactorZoomedOut: number; shapes: Record; highlightOutlineWidth: number; diff --git a/src/client/render/gl/shaders/structure/structure.vert.glsl b/src/client/render/gl/shaders/structure/structure.vert.glsl index a0b1a6d756..b710066372 100644 --- a/src/client/render/gl/shaders/structure/structure.vert.glsl +++ b/src/client/render/gl/shaders/structure/structure.vert.glsl @@ -12,6 +12,7 @@ uniform float uZoom; uniform float uIconSize; uniform float uDotsThreshold; +uniform float uDotScale; uniform float uScaleFactor; uniform float uShapeScales[ATLAS_COLS]; uniform float uIconFills[ATLAS_COLS]; @@ -36,7 +37,7 @@ void main() { float iconScale; if (uZoom <= uDotsThreshold) { - iconScale = 1.0 / 2.5; + iconScale = uDotScale; } else { iconScale = min(1.0, uZoom / uScaleFactor); } From e87e2cd58ca52659469944baa3a5abdba00f0848 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 17:34:53 -0700 Subject: [PATCH 07/34] add iconGrowZoom for structures that scale with deep zoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously structures capped at iconScale = 1.0 once zoomed past iconScaleFactorZoomedOut, staying at a fixed pixel size no matter how far you zoomed in. They felt overlaid on the map instead of part of it. Add a third zoom band controlled by structure.iconGrowZoom. Past this threshold iconScale = uZoom / iconGrowZoom — structures grow with the canvas (world-anchored, fixed map-area coverage). Plumbed via the uIconGrowZoom uniform on StructurePass. Default 7 keeps normal play unchanged; only kicks in at deep zoom. --- src/client/render/gl/passes/structure-pass.ts | 3 +++ src/client/render/gl/render-settings.json | 5 +++-- src/client/render/gl/render-settings.ts | 7 +++++++ src/client/render/gl/shaders/structure/structure.vert.glsl | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/client/render/gl/passes/structure-pass.ts b/src/client/render/gl/passes/structure-pass.ts index ea186f0eae..d5874fad85 100644 --- a/src/client/render/gl/passes/structure-pass.ts +++ b/src/client/render/gl/passes/structure-pass.ts @@ -76,6 +76,7 @@ export class StructurePass { private uDotsThreshold: WebGLUniformLocation; private uDotScale: WebGLUniformLocation; private uScaleFactor: WebGLUniformLocation; + private uIconGrowZoom: WebGLUniformLocation; private uShapeScales: WebGLUniformLocation; private uIconFills: WebGLUniformLocation; private uGhostAlpha: WebGLUniformLocation; @@ -147,6 +148,7 @@ export class StructurePass { "uDotsThreshold", )!; this.uScaleFactor = gl.getUniformLocation(this.program, "uScaleFactor")!; + this.uIconGrowZoom = gl.getUniformLocation(this.program, "uIconGrowZoom")!; this.uShapeScales = gl.getUniformLocation(this.program, "uShapeScales")!; this.uIconFills = gl.getUniformLocation(this.program, "uIconFills")!; this.uGhostAlpha = gl.getUniformLocation(this.program, "uGhostAlpha")!; @@ -334,6 +336,7 @@ export class StructurePass { gl.uniform1f(this.uDotsThreshold, ss.dotsZoomThreshold); gl.uniform1f(this.uDotScale, ss.dotScale); gl.uniform1f(this.uScaleFactor, ss.iconScaleFactorZoomedOut); + gl.uniform1f(this.uIconGrowZoom, ss.iconGrowZoom); // Build per-structure uniform arrays from settings, ordered by atlas column const scales = new Float32Array(ATLAS_COLS); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index b9da108d47..2a0105c4cc 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -82,10 +82,11 @@ "railAlpha": 1 }, "structure": { - "iconSize": 65, + "iconSize": 50, "dotsZoomThreshold": 1.2, "dotScale": 0.3, - "iconScaleFactorZoomedOut": 3.5, + "iconScaleFactorZoomedOut": 3, + "iconGrowZoom": 7, "shapes": { "City": { "scale": 1, diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts index adbd373cf0..1b661dea93 100644 --- a/src/client/render/gl/render-settings.ts +++ b/src/client/render/gl/render-settings.ts @@ -89,6 +89,13 @@ export interface RenderSettings { /** Icon size multiplier when zoomed out past dotsZoomThreshold. */ dotScale: number; iconScaleFactorZoomedOut: number; + /** + * Zoom level at which structures begin growing with the canvas. + * Below this zoom, structures stay at a fixed screen size (capped). + * Above this zoom, they grow proportionally to zoom — i.e. world-anchored, + * so they cover a fixed area of the map. + */ + iconGrowZoom: number; shapes: Record; highlightOutlineWidth: number; highlightDimAlpha: number; diff --git a/src/client/render/gl/shaders/structure/structure.vert.glsl b/src/client/render/gl/shaders/structure/structure.vert.glsl index b710066372..41be22f301 100644 --- a/src/client/render/gl/shaders/structure/structure.vert.glsl +++ b/src/client/render/gl/shaders/structure/structure.vert.glsl @@ -14,6 +14,7 @@ uniform float uIconSize; uniform float uDotsThreshold; uniform float uDotScale; uniform float uScaleFactor; +uniform float uIconGrowZoom; uniform float uShapeScales[ATLAS_COLS]; uniform float uIconFills[ATLAS_COLS]; @@ -38,6 +39,11 @@ void main() { float iconScale; if (uZoom <= uDotsThreshold) { iconScale = uDotScale; + } else if (uZoom >= uIconGrowZoom) { + // World-anchored: grow proportionally to zoom so the structure covers a + // fixed area of the map. Past this zoom, structures should feel like + // they're "on" the canvas rather than overlaid at constant pixel size. + iconScale = uZoom / uIconGrowZoom; } else { iconScale = min(1.0, uZoom / uScaleFactor); } From 8955be766774c60252acd48ba95e1de40c387fef Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 17:45:29 -0700 Subject: [PATCH 08/34] fix: store embargoes as smallID numbers (drop string[] wart) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlayerState.embargoes was string[] of stringified smallIDs — the renderer parsed each entry with parseInt() to use as an array index. Flagged in the integration handoff as something that should be number[]. Switch to number[] end-to-end: renderer type, relation-matrix derive (no parseInt), PlayerView.setEmbargoSmallIDs / hasEmbargoAgainst (numeric Array.includes, no String() temporaries), and GameView's embargo translation pass. Also updates the PlayerView test that pinned the old format. --- src/client/render/frame/derive/relation-matrix.ts | 3 +-- src/client/render/types/renderer.ts | 2 +- src/client/view/GameView.ts | 6 +++--- src/client/view/PlayerView.ts | 13 ++++++------- tests/client/view/PlayerView.test.ts | 6 +++--- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/client/render/frame/derive/relation-matrix.ts b/src/client/render/frame/derive/relation-matrix.ts index f0eca51eb3..ca3b7dcb8c 100644 --- a/src/client/render/frame/derive/relation-matrix.ts +++ b/src/client/render/frame/derive/relation-matrix.ts @@ -67,8 +67,7 @@ export function buildRelationMatrix( } if (ps.embargoes) { - for (const eStr of ps.embargoes) { - const eID = parseInt(eStr, 10); + for (const eID of ps.embargoes) { if (eID > 0 && eID < RELATION_SIZE) { matrix[sid * RELATION_SIZE + eID] = RELATION_EMBARGO; matrix[eID * RELATION_SIZE + sid] = RELATION_EMBARGO; diff --git a/src/client/render/types/renderer.ts b/src/client/render/types/renderer.ts index 14b873207f..be262a75f9 100644 --- a/src/client/render/types/renderer.ts +++ b/src/client/render/types/renderer.ts @@ -63,7 +63,7 @@ export interface PlayerState { hasSpawned: boolean; lastDeleteUnitTick: number; allies: number[]; - embargoes: string[]; + embargoes: number[]; targets: number[]; outgoingAttacks: AttackData[]; incomingAttacks: AttackData[]; diff --git a/src/client/view/GameView.ts b/src/client/view/GameView.ts index 0e70bb85cf..497ad7463d 100644 --- a/src/client/view/GameView.ts +++ b/src/client/view/GameView.ts @@ -342,14 +342,14 @@ export class GameView implements GameMap { gu.updates[GameUpdateType.Player].forEach((pu) => { const player = this._players.get(pu.id); if (player === undefined) return; - const smallIDStrings: string[] = []; + const smallIDs: number[] = []; for (const otherPlayerID of pu.embargoes) { const otherPV = this._players.get(otherPlayerID); if (otherPV !== undefined) { - smallIDStrings.push(String(otherPV.smallID())); + smallIDs.push(otherPV.smallID()); } } - player.setEmbargoSmallIDs(smallIDStrings); + player.setEmbargoSmallIDs(smallIDs); }); if (this._myClientID) { diff --git a/src/client/view/PlayerView.ts b/src/client/view/PlayerView.ts index 6c2272d99a..2e49d9916c 100644 --- a/src/client/view/PlayerView.ts +++ b/src/client/view/PlayerView.ts @@ -64,8 +64,8 @@ function staticFromUpdate(pu: PlayerUpdate): PlayerStatic { } function stateFromUpdate(pu: PlayerUpdate): PlayerState { - // embargoes: Set on the wire, but the renderer expects - // stringified smallIDs. GameView fills these in via setEmbargoes() because + // embargoes: Set on the wire, but the renderer stores + // smallIDs (numbers). GameView fills these in via setEmbargoes() because // it has the PlayerID → smallID lookup table. return { smallID: pu.smallID, @@ -249,9 +249,9 @@ export class PlayerView { applyStateUpdate(this.state, pu); } - /** Set the renderer-format embargoes (stringified smallIDs). */ - setEmbargoSmallIDs(smallIDStrings: string[]): void { - this.state.embargoes = smallIDStrings; + /** Set the renderer-format embargoes (smallIDs). */ + setEmbargoSmallIDs(smallIDs: number[]): void { + this.state.embargoes = smallIDs; } territoryColor(tile?: TileRef): Colord { @@ -493,8 +493,7 @@ export class PlayerView { } hasEmbargoAgainst(other: PlayerView): boolean { - const otherSmallIDStr = String(other.smallID()); - return this.state.embargoes.includes(otherSmallIDStr); + return this.state.embargoes.includes(other.smallID()); } hasEmbargo(other: PlayerView): boolean { diff --git a/tests/client/view/PlayerView.test.ts b/tests/client/view/PlayerView.test.ts index 4acea7a618..c41b0d5be2 100644 --- a/tests/client/view/PlayerView.test.ts +++ b/tests/client/view/PlayerView.test.ts @@ -152,8 +152,8 @@ describe("PlayerView relations", () => { aTeam?: string; bTeam?: string; // Embargoes are renderer-format: stringified smallIDs of the OTHER player. - aEmbargoSmallIDs?: string[]; - bEmbargoSmallIDs?: string[]; + aEmbargoSmallIDs?: number[]; + bEmbargoSmallIDs?: number[]; aOutgoingReq?: string[]; } = {}, ) { @@ -207,7 +207,7 @@ describe("PlayerView relations", () => { it("hasEmbargoAgainst / hasEmbargo are symmetric on the second", () => { // a embargoes b — by smallID (renderer format) - const aEmbargoesB = pair(1, 2, { aEmbargoSmallIDs: ["2"] }); + const aEmbargoesB = pair(1, 2, { aEmbargoSmallIDs: [2] }); // One-way directional embargo from a expect(aEmbargoesB.a.hasEmbargoAgainst(aEmbargoesB.b)).toBe(true); expect(aEmbargoesB.b.hasEmbargoAgainst(aEmbargoesB.a)).toBe(false); From 2fec1e994e391e29c685f69b467f0785b7809c17 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 18:51:34 -0700 Subject: [PATCH 09/34] retire DynamicUILayer, restore warship UX on WebGL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DynamicUILayer was a canvas2D mix of: bonus-event gold/troops popups (already duplicated by WebGL BonusPopupPass), nuke/transport telegraph indicators (duplicated by WebGL passes), and a warship move-indicator chevron drawn via MoveIndicatorUI. Delete the layer outright along with its three orphan UI helpers (MoveIndicatorUI, NavalTarget, NukeTelegraph). That deletion uncovered a pre-existing bug from the "migrate away from canvas" commit: warship select/move no longer worked. The deleted UnitLayer had owned the click flow that emits MoveWarshipIntentEvent. Re-add the flow inside UILayer (which already tracks selected / multi-selected warships for its selection box): MouseUpEvent → move-multi → move-single → select-nearest, plus shift+drag box complete and select-all hotkey. Wire MoveWarshipIntentEvent → view.showMoveIndicator(tx, ty, ownerID) in mountWebGLDebugRenderer so the WebGL MoveIndicatorPass draws the converging-chevron animation at the move target, colored by the warship's owner. mountWebGLDebugRenderer now takes gameView + eventBus to resolve the owner and subscribe. --- src/client/ClientGameRunner.ts | 19 +++ src/client/graphics/GameRenderer.ts | 2 - src/client/graphics/layers/DynamicUILayer.ts | 165 ------------------ src/client/graphics/layers/UILayer.ts | 171 +++++++++++++++++++ src/client/graphics/ui/MoveIndicatorUI.ts | 81 --------- src/client/graphics/ui/NavalTarget.ts | 132 -------------- src/client/graphics/ui/NukeTelegraph.ts | 113 ------------ 7 files changed, 190 insertions(+), 493 deletions(-) delete mode 100644 src/client/graphics/layers/DynamicUILayer.ts delete mode 100644 src/client/graphics/ui/MoveIndicatorUI.ts delete mode 100644 src/client/graphics/ui/NavalTarget.ts delete mode 100644 src/client/graphics/ui/NukeTelegraph.ts diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 711fe43d7b..47933bd732 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -47,6 +47,7 @@ import { import { endGame, startGame, startTime } from "./LocalPersistantStats"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; import { + MoveWarshipIntentEvent, SendAllianceExtensionIntentEvent, SendAllianceRequestIntentEvent, SendAttackIntentEvent, @@ -231,6 +232,8 @@ export function joinLobby( function mountWebGLDebugRenderer( terrainMap: TerrainMapData, transformHandler: import("./graphics/TransformHandler").TransformHandler, + gameView: GameView, + eventBus: EventBus, ): { builder: WebGLFrameBuilder; syncCamera: () => void } { const gameMap = terrainMap.gameMap; const mapWidth = gameMap.width(); @@ -332,6 +335,20 @@ function mountWebGLDebugRenderer( (window as unknown as { __webglView?: unknown }).__webglView = view; + // Move-target chevrons: when the player issues a warship move, show the + // animated chevron pass at the target tile. The renderer needs the target's + // tile x/y and the warship's owner smallID (so the chevrons use the right + // color). + eventBus.on(MoveWarshipIntentEvent, (e) => { + const tile = e.tile; + const tx = gameView.x(tile); + const ty = gameView.y(tile); + // Resolve owner via the first unit in the move set. + const firstUnit = gameView.unit(e.unitIds[0]); + if (firstUnit === undefined) return; + view.showMoveIndicator(tx, ty, firstUnit.owner().smallID()); + }); + return { builder: new WebGLFrameBuilder(view), syncCamera }; } @@ -389,6 +406,8 @@ async function createClientGame( const { builder: webglBuilder, syncCamera } = mountWebGLDebugRenderer( gameMap, gameRenderer.transformHandler, + gameView, + eventBus, ); gameRenderer.onPreRender = syncCamera; diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index a25bff2787..2ad7dcdbed 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -13,7 +13,6 @@ import { BuildMenu } from "./layers/BuildMenu"; import { ChatDisplay } from "./layers/ChatDisplay"; import { ChatModal } from "./layers/ChatModal"; import { ControlPanel } from "./layers/ControlPanel"; -import { DynamicUILayer } from "./layers/DynamicUILayer"; import { EmojiTable } from "./layers/EmojiTable"; import { EventsDisplay } from "./layers/EventsDisplay"; import { GameLeftSidebar } from "./layers/GameLeftSidebar"; @@ -265,7 +264,6 @@ export function createRenderer( const layers: Layer[] = [ new UILayer(game, eventBus, transformHandler), new StructureIconsLayer(game, eventBus, uiState, transformHandler), - new DynamicUILayer(game, transformHandler, eventBus), new NameLayer(game, transformHandler, eventBus), new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings), eventsDisplay, diff --git a/src/client/graphics/layers/DynamicUILayer.ts b/src/client/graphics/layers/DynamicUILayer.ts deleted file mode 100644 index a6200e360a..0000000000 --- a/src/client/graphics/layers/DynamicUILayer.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { renderNumber } from "src/client/Utils"; -import { EventBus } from "src/core/EventBus"; -import { UnitType } from "src/core/game/Game"; -import { BonusEventUpdate, GameUpdateType } from "src/core/game/GameUpdates"; -import type { GameView, UnitView } from "../../../core/game/GameView"; -import { MoveWarshipIntentEvent } from "../../Transport"; -import { TransformHandler } from "../TransformHandler"; -import { MoveIndicatorUI } from "../ui/MoveIndicatorUI"; -import { NavalTarget } from "../ui/NavalTarget"; -import { NukeTelegraph } from "../ui/NukeTelegraph"; -import { TextIndicator } from "../ui/TextIndicator"; -import { UIElement } from "../ui/UIElement"; -import { Layer } from "./Layer"; - -const TEXT_OFFSET_Y = -5; -const TEXT_STACK_SPACING = 8; - -export class DynamicUILayer implements Layer { - private readonly uiElements: Array = []; - private lastRefresh = Date.now(); - - constructor( - private readonly game: GameView, - private transformHandler: TransformHandler, - private eventBus: EventBus, - ) {} - - init() { - // Listen for warship move clicks for MoveIndicatorUI - this.eventBus.on(MoveWarshipIntentEvent, (e) => { - const x = this.game.x(e.tile); - const y = this.game.y(e.tile); - this.uiElements.push(new MoveIndicatorUI(this.transformHandler, x, y)); - }); - } - - shouldTransform(): boolean { - return false; - } - - tick() { - if (!this.game.config().userSettings()?.fxLayer()) { - return; - } - - const updates = this.game.updatesSinceLastTick(); - if (!updates) return; - - updates[GameUpdateType.Unit]?.forEach((unit) => { - const unitView = this.game.unit(unit.id); - if (!unitView) return; - this.onUnitEvent(unitView); - }); - - updates[GameUpdateType.BonusEvent]?.forEach((bonusEvent) => { - if (bonusEvent === undefined) return; - this.onBonusEvent(bonusEvent); - }); - } - - onBonusEvent(bonus: BonusEventUpdate) { - // Only display text fx for the current player - if (this.game.player(bonus.player) !== this.game.myPlayer()) { - return; - } - const tile = bonus.tile; - const x = this.game.x(tile); - let y = this.game.y(tile) + TEXT_OFFSET_Y; - const gold = bonus.gold; - const troops = bonus.troops; - - if (gold !== 0) { - this.addNumber(gold, x, y, 1000, 10); - y += TEXT_STACK_SPACING; // increase y so the next popup starts below - } - - if (troops !== 0) { - this.addNumber(troops, x, y, 1000, 10); - } - } - - onUnitEvent(unit: UnitView) { - switch (unit.type()) { - case UnitType.HydrogenBomb: - case UnitType.AtomBomb: { - this.onBombEvent(unit); - break; - } - case UnitType.TransportShip: { - this.onTransportShipEvent(unit); - break; - } - } - } - - onBombEvent(unit: UnitView) { - const myPlayer = this.game.myPlayer(); - if (!myPlayer) { - return; - } - if ( - this.createdThisTick(unit) && - (unit.owner() === myPlayer || unit.owner().isOnSameTeam(myPlayer)) - ) { - const target = new NukeTelegraph(this.transformHandler, this.game, unit); - this.uiElements.push(target); - } - } - - onTransportShipEvent(unit: UnitView) { - const myPlayer = this.game.myPlayer(); - if (!myPlayer) { - return; - } - if (this.createdThisTick(unit) && unit.owner() === myPlayer) { - const target = new NavalTarget(this.transformHandler, this.game, unit); - this.uiElements.push(target); - } - } - - renderLayer(context: CanvasRenderingContext2D) { - const now = Date.now(); - const dt = now - this.lastRefresh; - this.lastRefresh = now; - if (this.game.config().userSettings()?.fxLayer()) { - this.renderUIElements(context, dt); - } - } - - renderUIElements(context: CanvasRenderingContext2D, delta: number) { - for (let i = this.uiElements.length - 1; i >= 0; i--) { - if (!this.uiElements[i].render(context, delta)) { - this.uiElements.splice(i, 1); - } - } - } - - private createdThisTick(unit: UnitView): boolean { - return unit.createdAt() === this.game.ticks(); - } - - private addNumber( - num: bigint | number, - x: number, - y: number, - duration: number, - riseDistance: number, - ) { - if (BigInt(num) === 0n) return; // Don't show anything for 0 - const absNum = - typeof num === "bigint" ? (num < 0n ? -num : num) : Math.abs(num); - const shortened = renderNumber(absNum, 0); - const sign = num >= 0 ? "+" : "-"; - this.uiElements.push( - new TextIndicator( - this.transformHandler, - `${sign} ${shortened}`, - x, - y, - duration, - riseDistance, - ), - ); - } -} diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 1338baad45..0b1aafe366 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -1,18 +1,27 @@ import { Colord } from "colord"; import { Theme } from "src/core/configuration/Theme"; +import { Cell } from "src/core/game/Game"; import { EventBus } from "../../../core/EventBus"; import { UnitType } from "../../../core/game/Game"; +import { TileRef } from "../../../core/game/GameMap"; import { GameView, UnitView } from "../../../core/game/GameView"; import { CloseViewEvent, + ContextMenuEvent, + MouseUpEvent, + SelectAllWarshipsEvent, + TouchEvent, UnitSelectionEvent, WarshipSelectionBoxCancelEvent, WarshipSelectionBoxCompleteEvent, WarshipSelectionBoxUpdateEvent, } from "../../InputHandler"; +import { MoveWarshipIntentEvent } from "../../Transport"; import { TransformHandler } from "../TransformHandler"; import { Layer } from "./Layer"; +const WARSHIP_SELECTION_RADIUS = 10; + /** * Layer responsible for drawing UI elements that overlay the game. * Currently: warship selection boxes + drag-rectangle selection. @@ -116,9 +125,171 @@ export class UILayer implements Layer { this.eventBus.on(WarshipSelectionBoxCompleteEvent, clearBox); this.eventBus.on(WarshipSelectionBoxCancelEvent, clearBox); this.eventBus.on(CloseViewEvent, clearBox); + + // Warship select/move click flow (previously in the deleted UnitLayer). + this.eventBus.on(MouseUpEvent, (e) => this.onMouseUp(e)); + this.eventBus.on(TouchEvent, (e) => this.onTouch(e)); + this.eventBus.on(WarshipSelectionBoxCompleteEvent, (e) => + this.onSelectionBoxComplete(e), + ); + this.eventBus.on(SelectAllWarshipsEvent, () => this.onSelectAllWarships()); + this.redraw(); } + /** + * Find player-owned warships near the given cell, sorted by distance. + */ + private findWarshipsNearCell(clickRef: TileRef): UnitView[] { + const myPlayer = this.game.myPlayer(); + if (!myPlayer) return []; + return this.game + .units(UnitType.Warship) + .filter( + (unit) => + unit.isActive() && + unit.owner() === myPlayer && + this.game.manhattanDist(unit.tile(), clickRef) <= + WARSHIP_SELECTION_RADIUS, + ) + .sort( + (a, b) => + this.game.manhattanDist(a.tile(), clickRef) - + this.game.manhattanDist(b.tile(), clickRef), + ); + } + + /** + * Resolve a left-click in the world: + * - multi-selected warships present + clicked water → move them all + * - single selected warship + clicked water → move it, then deselect + * - otherwise → if there's a nearby warship, select the closest one + */ + private onMouseUp( + event: MouseUpEvent, + clickRef?: TileRef, + nearbyWarships?: UnitView[], + ) { + if (clickRef === undefined) { + const cell = this.transformHandler.screenToWorldCoordinates( + event.x, + event.y, + ); + if (!this.game.isValidCoord(cell.x, cell.y)) return; + clickRef = this.game.ref(cell.x, cell.y); + } + if (!this.game.isWater(clickRef)) return; + + if (this.multiSelectedWarships.length > 0) { + const myPlayer = this.game.myPlayer(); + const activeIds = this.multiSelectedWarships + .filter((u) => u.isActive() && u.owner() === myPlayer) + .map((u) => u.id()); + + if (activeIds.length > 0) { + this.eventBus.emit(new MoveWarshipIntentEvent(activeIds, clickRef)); + } + this.eventBus.emit(new UnitSelectionEvent(null, false)); + return; + } + + if (this.selectedUnit) { + this.eventBus.emit( + new MoveWarshipIntentEvent([this.selectedUnit.id()], clickRef), + ); + this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false)); + return; + } + + nearbyWarships ??= this.findWarshipsNearCell(clickRef); + if (nearbyWarships.length > 0) { + this.eventBus.emit(new UnitSelectionEvent(nearbyWarships[0], true)); + } + } + + /** + * Touch handler mirroring mouse-up. On dry land with no selection, falls + * back to opening the radial menu. + */ + private onTouch(event: TouchEvent) { + const cell = this.transformHandler.screenToWorldCoordinates( + event.x, + event.y, + ); + if (!this.game.isValidCoord(cell.x, cell.y)) return; + + const clickRef = this.game.ref(cell.x, cell.y); + if (this.game.inSpawnPhase()) { + if (!this.game.isWater(clickRef)) { + this.eventBus.emit(new MouseUpEvent(event.x, event.y)); + } + return; + } + if (!this.game.isWater(clickRef)) { + this.eventBus.emit(new ContextMenuEvent(event.x, event.y)); + return; + } + if (this.selectedUnit || this.multiSelectedWarships.length > 0) { + this.onMouseUp(new MouseUpEvent(event.x, event.y), clickRef); + return; + } + const nearbyWarships = this.findWarshipsNearCell(clickRef); + if (nearbyWarships.length > 0) { + this.onMouseUp( + new MouseUpEvent(event.x, event.y), + clickRef, + nearbyWarships, + ); + } else { + this.eventBus.emit(new ContextMenuEvent(event.x, event.y)); + } + } + + /** + * Resolve a shift+drag selection box: gather all player-owned warships + * whose screen position falls inside the rectangle. + */ + private onSelectionBoxComplete(event: WarshipSelectionBoxCompleteEvent) { + const x1 = Math.min(event.startX, event.endX); + const y1 = Math.min(event.startY, event.endY); + const x2 = Math.max(event.startX, event.endX); + const y2 = Math.max(event.startY, event.endY); + + const myPlayer = this.game.myPlayer(); + if (!myPlayer) return; + + const selected = this.game.units(UnitType.Warship).filter((unit) => { + if (!unit.isActive() || unit.owner() !== myPlayer) return false; + const screen = this.transformHandler.worldToScreenCoordinates( + new Cell(this.game.x(unit.tile()), this.game.y(unit.tile())), + ); + return ( + screen.x >= x1 && screen.x <= x2 && screen.y >= y1 && screen.y <= y2 + ); + }); + + // Clear single selection if we got a box selection + if (selected.length > 0 && this.selectedUnit) { + this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false)); + } + this.eventBus.emit(new UnitSelectionEvent(null, true, selected)); + } + + private onSelectAllWarships() { + const myPlayer = this.game.myPlayer(); + if (!myPlayer) return; + + const allWarships = this.game + .units(UnitType.Warship) + .filter((u) => u.isActive() && u.owner() === myPlayer); + if (allWarships.length === 0) return; + + if (this.selectedUnit) { + this.eventBus.emit(new UnitSelectionEvent(this.selectedUnit, false)); + } + this.eventBus.emit(new UnitSelectionEvent(null, true, allWarships)); + } + renderLayer(context: CanvasRenderingContext2D) { context.drawImage( this.canvas, diff --git a/src/client/graphics/ui/MoveIndicatorUI.ts b/src/client/graphics/ui/MoveIndicatorUI.ts deleted file mode 100644 index 8da36b7d4a..0000000000 --- a/src/client/graphics/ui/MoveIndicatorUI.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Cell } from "src/core/game/Game"; -import { TransformHandler } from "../TransformHandler"; -import { UIElement } from "./UIElement"; - -/** - * move indicator fx for warship, similar to moba games. - */ -export class MoveIndicatorUI implements UIElement { - private lifeTime = 0; - private readonly duration = 800; // ms - private readonly startRadius = 13; // starting distance from center (screen pixels) - private readonly chevronSize = 5; // size in screen pixels - private readonly cell: Cell; - - constructor( - private transformHandler: TransformHandler, - public x: number, - public y: number, - ) { - this.cell = new Cell(this.x + 0.5, this.y + 0.5); - } - - render(ctx: CanvasRenderingContext2D, delta: number): boolean { - this.lifeTime += delta; - if (this.lifeTime >= this.duration) return false; - - const t = this.lifeTime / this.duration; - const alpha = 1 - t; // fade out - - // Scale with zoom level (same pattern as NavalTarget) - const transformScale = this.transformHandler.scale; - const scale = transformScale > 10 ? 1 + (transformScale - 10) / 10 : 1; - - const radius = this.startRadius * scale * (1 - t * 0.7); // converge inward - const chevronSize = this.chevronSize * scale; - - // Get screen coordinates - const screenPos = this.transformHandler.worldToCanvasCoordinates(this.cell); - const centerX = screenPos.x; - const centerY = screenPos.y; - - ctx.save(); - ctx.globalAlpha = alpha; - ctx.strokeStyle = "#ff0000"; - ctx.lineWidth = 2 * scale; - ctx.lineCap = "round"; - ctx.lineJoin = "round"; - - // pre calculation of offsets - const tipOffset = chevronSize * 0.4; - const wingOffset = chevronSize * 0.6; - const width = chevronSize; - - ctx.beginPath(); - - // Top (pointing down) - ctx.moveTo(centerX - width, centerY - radius - wingOffset); - ctx.lineTo(centerX, centerY - radius + tipOffset); - ctx.lineTo(centerX + width, centerY - radius - wingOffset); - - // Bottom (pointing up) - ctx.moveTo(centerX - width, centerY + radius + wingOffset); - ctx.lineTo(centerX, centerY + radius - tipOffset); - ctx.lineTo(centerX + width, centerY + radius + wingOffset); - - // Left (pointing right) - ctx.moveTo(centerX - radius - wingOffset, centerY - width); - ctx.lineTo(centerX - radius + tipOffset, centerY); - ctx.lineTo(centerX - radius - wingOffset, centerY + width); - - // Right (pointing left) - ctx.moveTo(centerX + radius + wingOffset, centerY - width); - ctx.lineTo(centerX + radius - tipOffset, centerY); - ctx.lineTo(centerX + radius + wingOffset, centerY + width); - - ctx.stroke(); - - ctx.restore(); - return true; - } -} diff --git a/src/client/graphics/ui/NavalTarget.ts b/src/client/graphics/ui/NavalTarget.ts deleted file mode 100644 index 0e88487df5..0000000000 --- a/src/client/graphics/ui/NavalTarget.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Cell, UnitType } from "src/core/game/Game"; -import { GameView, UnitView } from "src/core/game/GameView"; -import { TransformHandler } from "../TransformHandler"; -import { UIElement } from "./UIElement"; - -const BASE_ALPHA = 0.9; -const SHADOW_OFFSET_Y = 2; - -/** - * Draw a simple zoom-aware target - */ -export class Target implements UIElement { - private offset = 0; - private readonly rotationSpeed = 20; - private readonly dashSize: number; - private readonly outerRadius: number; - private readonly cell: Cell; - private readonly animationDuration = 150; - private animationElapsedTime = 0; - protected ended: boolean = false; - protected lifeTime: number = 0; - - constructor( - private transformHandler: TransformHandler, - public x: number, - public y: number, - private radius: number, - ) { - this.outerRadius = radius * 2 - 4; - // 2 dashes per circle, with a 10 pixel gap - this.dashSize = Math.PI * this.outerRadius - 10; - this.cell = new Cell(this.x + 0.5, this.y + 0.5); - } - render(ctx: CanvasRenderingContext2D, delta: number): boolean { - this.lifeTime += delta; - - if (this.ended) { - this.animationElapsedTime += delta; - if (this.animationElapsedTime >= this.animationDuration) return false; - } - - let t: number; - if (this.ended) { - // end animation - t = Math.max(0, 1 - this.animationElapsedTime / this.animationDuration); - } else { - t = 1; // No start fade feels more reactive - } - const alpha = Math.max(0, Math.min(1, BASE_ALPHA * t)); - - const screenPos = this.transformHandler.worldToCanvasCoordinates(this.cell); - screenPos.x = Math.round(screenPos.x); - screenPos.y = Math.round(screenPos.y); - const transformScale = this.transformHandler.scale; - const scale = transformScale > 10 ? 1 + (transformScale - 10) / 10 : 1; - this.offset += this.rotationSpeed * (delta / 1000); - - ctx.save(); - ctx.globalAlpha = alpha; - ctx.lineWidth = 1; - ctx.strokeStyle = `rgba(255,0,0,${alpha})`; - - this.drawInnerRing(ctx, screenPos.x, screenPos.y, scale); - this.drawOuterRing(ctx, screenPos.x, screenPos.y, scale); - - ctx.restore(); - return true; - } - - private drawInnerRing( - ctx: CanvasRenderingContext2D, - x: number, - y: number, - scale: number, - ) { - ctx.beginPath(); - ctx.lineWidth = 2; - ctx.lineDashOffset = this.offset * scale; - ctx.setLineDash([8 * scale, 8 * scale]); - ctx.arc(x, y, this.radius * scale, 0, Math.PI * 2); - ctx.stroke(); - } - - private drawOuterRing( - ctx: CanvasRenderingContext2D, - x: number, - y: number, - scale: number, - ) { - ctx.beginPath(); - ctx.lineWidth = 4 * scale; - ctx.lineDashOffset = (-this.offset / 2) * scale; - ctx.setLineDash([this.dashSize * scale, 10 * scale]); - ctx.arc(x, y, this.outerRadius * scale, 0, Math.PI * 2); - ctx.stroke(); - - // Small shadow under the outer circle - ctx.beginPath(); - ctx.strokeStyle = `rgba(0,0,0,0.2)`; - ctx.arc(x, y + SHADOW_OFFSET_Y, this.outerRadius * scale, 0, Math.PI * 2); - ctx.stroke(); - } -} - -/** - * Bind a target to a naval invasion - */ -export class NavalTarget extends Target { - constructor( - transformHandler: TransformHandler, - readonly game: GameView, - private unit: UnitView, - ) { - const tile = unit.targetTile(); - if (tile === undefined) { - throw new Error("NavalTarget requires a target tile"); - } - super(transformHandler, game.x(tile), game.y(tile), 10); - } - - render(ctx: CanvasRenderingContext2D, delta: number): boolean { - if ( - !this.ended && - (!this.unit.isActive() || - (this.unit.type() === UnitType.TransportShip && - this.unit.transportShipState().isRetreating)) - ) { - this.ended = true; - } - return super.render(ctx, delta); - } -} diff --git a/src/client/graphics/ui/NukeTelegraph.ts b/src/client/graphics/ui/NukeTelegraph.ts deleted file mode 100644 index 1d54a85670..0000000000 --- a/src/client/graphics/ui/NukeTelegraph.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { Cell } from "src/core/game/Game"; -import { GameView, UnitView } from "src/core/game/GameView"; -import { TransformHandler } from "../TransformHandler"; -import { UIElement } from "./UIElement"; - -const OUTER_EXPAND = 20; -const FILL_ALPHA_OFFSET = 0.6; - -/** - * Draw an area with two disks - */ -export class CircleArea implements UIElement { - private offset = 0; - private readonly dashSize: number; - private readonly rotationSpeed = 20; - private readonly baseAlpha = 0.9; - private readonly cell: Cell; - private readonly animationDuration = 150; - protected ended: boolean = false; - protected lifeTime: number = 0; - - constructor( - private transformHandler: TransformHandler, - public x: number, - public y: number, - private innerDiameter: number, - private outerDiameter: number, - ) { - this.cell = new Cell(this.x + 0.5, this.y + 0.5); - // Compute a dash length that produces N dashes around the circle - const numDash = Math.max(1, Math.floor(this.outerDiameter / 3)); - this.dashSize = (Math.PI / numDash) * this.outerDiameter; - } - render(ctx: CanvasRenderingContext2D, delta: number): boolean { - this.lifeTime += delta; - - if (this.ended && this.lifeTime >= this.animationDuration) return false; - let t: number; - if (this.ended) { - t = Math.max(0, 1 - this.lifeTime / this.animationDuration); - } else { - t = Math.min(1, this.lifeTime / this.animationDuration); - } - const alpha = Math.max(0, Math.min(1, this.baseAlpha * t)); - const scale = this.transformHandler.scale; - - const innerDiameter = - (this.innerDiameter / 2) * (1 - t) + this.innerDiameter * t; - const screenPos = this.transformHandler.worldToCanvasCoordinates(this.cell); - screenPos.x = Math.round(screenPos.x); - screenPos.y = Math.round(screenPos.y); - - ctx.save(); - ctx.globalAlpha = alpha; - ctx.lineWidth = 2; - ctx.strokeStyle = `rgba(255,0,0,${alpha})`; - ctx.fillStyle = `rgba(255,0,0,${Math.max(0, alpha - FILL_ALPHA_OFFSET)})`; - - // Inner circle - ctx.beginPath(); - ctx.lineWidth = 1; - ctx.arc(screenPos.x, screenPos.y, innerDiameter * scale, 0, Math.PI * 2); - ctx.stroke(); - ctx.fill(); - - // Outer circle - this.offset += this.rotationSpeed * (delta / 1000); - ctx.beginPath(); - ctx.strokeStyle = `rgba(255,0,0,${alpha})`; - ctx.lineWidth = Math.max(2, 1 * scale); - ctx.lineDashOffset = this.offset * scale; - ctx.setLineDash([this.dashSize * scale]); - const outerDiameter = - (this.outerDiameter + OUTER_EXPAND) * (1 - t) + this.outerDiameter * t; - ctx.arc(screenPos.x, screenPos.y, outerDiameter * scale, 0, Math.PI * 2); - ctx.stroke(); - - ctx.restore(); - return true; - } -} - -/** - * Bind a nuke destination to an area - */ -export class NukeTelegraph extends CircleArea { - constructor( - transformHandler: TransformHandler, - private readonly game: GameView, - private nuke: UnitView, - ) { - const tile = nuke.targetTile(); - if (tile === undefined) { - throw new Error("NukeArea requires a target tile"); - } - const magnitude = game.config().nukeMagnitudes(nuke.type()); - super( - transformHandler, - game.x(tile), - game.y(tile), - magnitude.inner, - magnitude.outer, - ); - } - - render(ctx: CanvasRenderingContext2D, delta: number): boolean { - if (!this.ended && !this.nuke.isActive()) { - this.ended = true; - this.lifeTime = 0; // reset lifetime to reuse animation logic - } - return super.render(ctx, delta); - } -} From 3481beba8ac3ea2616edc6a8b328e2f61f779553 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 19:03:38 -0700 Subject: [PATCH 10/34] delete canvas2D NameLayer; render names via WebGL NamePass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop drawing names on canvas2D — NamePass already gets the placement data (gameView.frameData().names) and lerps positions in-shader. Drop the runtime passEnabled.name=false override in ClientGameRunner, remove NameLayer from the layers list, and delete NameLayer.ts. Known gaps (deferred): - Player-uploaded flags not in the bundled atlas render as no-flag; needs a JIT atlas built at game start. - The shared computePlayerStatus is the replay variant, so the alliance / target / embargo / nukeTargetsMe status icons stay off for the local player's perspective. Needs a live-aware variant. --- src/client/ClientGameRunner.ts | 4 - src/client/graphics/GameRenderer.ts | 2 - src/client/graphics/layers/NameLayer.ts | 553 ------------------------ 3 files changed, 559 deletions(-) delete mode 100644 src/client/graphics/layers/NameLayer.ts diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 47933bd732..09047f3717 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -286,10 +286,6 @@ function mountWebGLDebugRenderer( captureCaf, ); - // Names are rendered by the existing HTML NameLayer; disable the renderer's - // NamePass to avoid drawing them twice. - view.getSettings().passEnabled.name = false; - window.addEventListener("keydown", (e) => { if (e.key === "\\") { glCanvas.style.display = diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 2ad7dcdbed..4580d4cbeb 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -24,7 +24,6 @@ import { Layer } from "./layers/Layer"; import { Leaderboard } from "./layers/Leaderboard"; import { MainRadialMenu } from "./layers/MainRadialMenu"; import { MultiTabModal } from "./layers/MultiTabModal"; -import { NameLayer } from "./layers/NameLayer"; import { PerformanceOverlay } from "./layers/PerformanceOverlay"; import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay"; import { PlayerPanel } from "./layers/PlayerPanel"; @@ -264,7 +263,6 @@ export function createRenderer( const layers: Layer[] = [ new UILayer(game, eventBus, transformHandler), new StructureIconsLayer(game, eventBus, uiState, transformHandler), - new NameLayer(game, transformHandler, eventBus), new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings), eventsDisplay, attacksDisplay, diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts deleted file mode 100644 index ff5741a784..0000000000 --- a/src/client/graphics/layers/NameLayer.ts +++ /dev/null @@ -1,553 +0,0 @@ -import { assetUrl } from "src/core/AssetUrls"; -import { Theme } from "src/core/configuration/Theme"; -import { EventBus } from "../../../core/EventBus"; -import { PseudoRandom } from "../../../core/PseudoRandom"; -import { Config } from "../../../core/configuration/Config"; -import { Cell } from "../../../core/game/Game"; -import { GameView, PlayerView } from "../../../core/game/GameView"; -import { UserSettings } from "../../../core/game/UserSettings"; -import { AlternateViewEvent } from "../../InputHandler"; -import { renderTroops } from "../../Utils"; -import { - ALLIANCE_ICON_ID, - AllianceProgressIconRefs, - createAllianceProgressIconRefs, - EMOJI_ICON_KIND, - getFirstPlacePlayer, - getPlayerIcons, - IMAGE_ICON_KIND, - PlayerIconDescriptor, - PlayerIconId, - TRAITOR_ICON_ID, - updateAllianceProgressIconRefs, -} from "../PlayerIcons"; -import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; - -const PLAYER_NAME = "player-name"; -const PLAYER_NAME_SPAN = "player-name-span"; -const PLAYER_TROOPS = "player-troops"; -const PLAYER_ICONS = "player-icons"; -const PLAYER_FLAG = "player-flag"; - -class RenderInfo { - public icons: Map = new Map(); - public allianceIconRefs: AllianceProgressIconRefs | null = null; - - constructor( - public player: PlayerView, - public lastRenderCalc: number, - public location: Cell | null, - public fontSize: number, - public fontColor: string, - public element: HTMLElement, - public nameDiv: HTMLDivElement, - public nameSpan: HTMLSpanElement, - public troopsDiv: HTMLDivElement, - public flagImg: HTMLImageElement, - public iconsDiv: HTMLDivElement, - public lastTransform: string = "", - ) {} -} - -export class NameLayer implements Layer { - private config: Config; - private lastChecked = 0; - private renderCheckRate = 100; - private renderRefreshRate = 500; - private rand = new PseudoRandom(10); - private renders: RenderInfo[] = []; - private seenPlayers: Set = new Set(); - private container: HTMLDivElement; - private theme: Theme; - private userSettings: UserSettings = new UserSettings(); - private isVisible: boolean = true; - private firstPlace: PlayerView | null = null; - private allianceDuration: number; - private alliancesDisabled: boolean = false; - private myPlayer: PlayerView | null = null; - private lastContainerTransform: string = ""; - private basePlayerTemplate: HTMLDivElement; - private iconTemplate: HTMLImageElement; - private iconCenterTemplate: HTMLImageElement; - private emojiTemplate: HTMLDivElement; - - constructor( - private game: GameView, - private transformHandler: TransformHandler, - private eventBus: EventBus, - ) {} - - shouldTransform(): boolean { - return false; - } - - redraw() {} // not affected by Canvas/WebGL context loss as this layer is DOM-based - - public init() { - this.container = document.createElement("div"); - this.container.style.position = "fixed"; - this.container.style.left = "50%"; - this.container.style.top = "50%"; - this.container.style.pointerEvents = "none"; - this.container.style.zIndex = "2"; - document.body.appendChild(this.container); - - // Add CSS keyframes for traitor icon flashing animation - // Append to container instead of document.head to keep styles scoped to this component - const style = document.createElement("style"); - style.textContent = ` - @keyframes traitorFlash { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.3; - } - } - `; - this.container.appendChild(style); - - this.myPlayer = this.game.myPlayer(); - this.config = this.game.config(); - this.theme = this.config.theme(); - - this.alliancesDisabled = this.config.disableAlliances(); - this.allianceDuration = Math.max(1, this.config.allianceDuration()); - - this.basePlayerTemplate = this.createBasePlayerElement(); - - this.iconTemplate = document.createElement("img"); - - this.iconCenterTemplate = document.createElement("img"); - this.iconCenterTemplate.style.position = "absolute"; - this.iconCenterTemplate.style.top = "50%"; - this.iconCenterTemplate.style.transform = "translateY(-50%)"; - - this.emojiTemplate = document.createElement("div"); - this.emojiTemplate.style.position = "absolute"; - this.emojiTemplate.style.top = "50%"; - this.emojiTemplate.style.transform = "translateY(-50%)"; - - this.eventBus.on(AlternateViewEvent, (e) => this.onAlternateViewChange(e)); - } - - private onAlternateViewChange(event: AlternateViewEvent) { - this.isVisible = !event.alternateView; - // Update visibility of all name elements immediately - for (const render of this.renders) { - this.updateElementVisibility(render); - } - } - - private updateElementVisibility(render: RenderInfo, baseSize?: number) { - if (!render.player.nameLocation() || !render.player.isAlive()) { - return; - } - - baseSize = - baseSize ?? Math.max(1, Math.floor(render.player.nameLocation().size)); - const size = this.transformHandler.scale * baseSize; - const isOnScreen = render.location - ? this.transformHandler.isOnScreen(render.location) - : false; - const maxZoomScale = 17; - - const display = - !this.isVisible || - size < 7 || - (this.transformHandler.scale > maxZoomScale && size > 100) || - !isOnScreen - ? "none" - : "flex"; - if (render.element.style.display !== display) { - render.element.style.display = display; - } - } - - getTickIntervalMs() { - return 1000; - } - - public tick() { - // Precompute the first-place player for performance - this.firstPlace = getFirstPlacePlayer(this.game); - - for (const player of this.game.playerViews()) { - if (player.isAlive()) { - if (!this.seenPlayers.has(player)) { - this.seenPlayers.add(player); - this.renders.push(this.createPlayerElement(player)); - } - } - } - } - - public renderLayer() { - const screenPosOld = this.transformHandler.worldToScreenCoordinates( - new Cell(0, 0), - ); - const screenPos = new Cell( - screenPosOld.x - window.innerWidth / 2, - screenPosOld.y - window.innerHeight / 2, - ); - const newTransform = `translate(${screenPos.x}px, ${screenPos.y}px) scale(${this.transformHandler.scale})`; - if (this.lastContainerTransform !== newTransform) { - this.container.style.transform = newTransform; - this.lastContainerTransform = newTransform; - } - - const now = Date.now(); - if (now > this.lastChecked + this.renderCheckRate) { - this.lastChecked = now; - - this.myPlayer ??= this.game.myPlayer(); - const transitiveTargets = this.myPlayer?.transitiveTargets() ?? []; - - for (const render of this.renders) { - this.renderPlayerInfo(render, transitiveTargets); - } - } - } - - private createBasePlayerElement(): HTMLDivElement { - const element = document.createElement("div"); - element.style.position = "absolute"; - element.style.flexDirection = "column"; - element.style.alignItems = "center"; - element.style.gap = "0px"; - // Start off invisible so it doesn't flash at 0,0 - element.style.display = "none"; - - const iconsDiv = document.createElement("div"); - iconsDiv.classList.add(PLAYER_ICONS); - iconsDiv.style.display = "flex"; - iconsDiv.style.gap = "4px"; - iconsDiv.style.justifyContent = "center"; - iconsDiv.style.alignItems = "center"; - iconsDiv.style.zIndex = "2"; - iconsDiv.style.opacity = "0.8"; - element.appendChild(iconsDiv); - - const nameDiv = document.createElement("div"); - nameDiv.classList.add(PLAYER_NAME); - nameDiv.style.whiteSpace = "nowrap"; - nameDiv.style.textOverflow = "ellipsis"; - nameDiv.style.zIndex = "3"; - nameDiv.style.display = "flex"; - nameDiv.style.justifyContent = "flex-end"; - nameDiv.style.alignItems = "center"; - - const flagImg = document.createElement("img"); - flagImg.classList.add(PLAYER_FLAG); - flagImg.style.opacity = "0.8"; - flagImg.style.zIndex = "1"; - flagImg.style.objectFit = "contain"; - flagImg.style.display = "none"; - nameDiv.appendChild(flagImg); - - const nameSpan = document.createElement("span"); - nameSpan.classList.add(PLAYER_NAME_SPAN); - nameDiv.appendChild(nameSpan); - element.appendChild(nameDiv); - - const troopsDiv = document.createElement("div"); - troopsDiv.classList.add(PLAYER_TROOPS); - troopsDiv.setAttribute("translate", "no"); - troopsDiv.style.zIndex = "3"; - troopsDiv.style.marginTop = "-5%"; - element.appendChild(troopsDiv); - - return element; - } - - private createPlayerElement(player: PlayerView): RenderInfo { - const element = this.basePlayerTemplate.cloneNode(true) as HTMLDivElement; - - // Queryselector expensive but this runs only once per player and better maintainable - const nameDiv = element.querySelector(`.${PLAYER_NAME}`) as HTMLDivElement; - const nameSpan = element.querySelector( - `.${PLAYER_NAME_SPAN}`, - ) as HTMLSpanElement; - const troopsDiv = element.querySelector( - `.${PLAYER_TROOPS}`, - ) as HTMLDivElement; - const flagImg = element.querySelector( - `.${PLAYER_FLAG}`, - ) as HTMLImageElement; - const iconsDiv = element.querySelector( - `.${PLAYER_ICONS}`, - ) as HTMLDivElement; - - const font = this.theme.font(); - nameDiv.style.fontFamily = font; - - const flag = player.cosmetics.flag; - if (flag) { - flagImg.src = assetUrl(flag); - flagImg.style.display = "block"; - } - - const renderInfo = new RenderInfo( - player, - 0, - null, - 0, - "", - element, - nameDiv, - nameSpan, - troopsDiv, - flagImg, - iconsDiv, - ); - - this.container.appendChild(element); - return renderInfo; - } - - renderPlayerInfo(render: RenderInfo, transitiveTargets: PlayerView[]) { - if (!render.player.nameLocation()) { - return; - } - if (!render.player.isAlive()) { - this.renders = this.renders.filter((r) => r !== render); - render.element.remove(); - return; - } - - // Update location and size, show or hide dependent on those - const nameLocation = render.player.nameLocation(); - const newX = nameLocation.x; - const newY = nameLocation.y; - - if ( - !render.location || - render.location.x !== newX || - render.location.y !== newY - ) { - render.location = new Cell(newX, newY); - } - - const baseSize = Math.max(1, Math.floor(nameLocation.size)); - this.updateElementVisibility(render, baseSize); - - if (render.element.style.display === "none") { - return; - } - - // Throttle further updates - const now = Date.now(); - if (now - render.lastRenderCalc <= this.renderRefreshRate) { - return; - } - render.lastRenderCalc = now + this.rand.nextInt(0, 100); - - // Update text sizes, content and color - render.fontSize = Math.max(4, Math.floor(baseSize * 0.4)); - render.nameDiv.style.fontSize = `${render.fontSize}px`; - render.nameDiv.style.lineHeight = `${render.fontSize}px`; - render.flagImg.style.height = `${render.fontSize}px`; - render.troopsDiv.style.fontSize = `${render.fontSize}px`; - - render.nameSpan.textContent = render.player.displayName(); - render.troopsDiv.textContent = renderTroops(render.player.troops()); - - const fontColor = this.theme.textColor(render.player); - if (render.fontColor !== fontColor) { - render.fontColor = fontColor; - render.nameDiv.style.color = fontColor; - render.troopsDiv.style.color = fontColor; - } - - // Handle icons - const iconSize = Math.min(render.fontSize * 1.5, 48); - const darkMode = this.userSettings.darkMode(); - const darkModeStr = darkMode.toString(); - - // Compute which icons should be shown for this player using shared logic - const icons = getPlayerIcons({ - game: this.game, - player: render.player, - includeAllianceIcon: true, - firstPlace: this.firstPlace, - darkMode: darkMode, - alliancesDisabled: this.alliancesDisabled, - transitiveTargets: transitiveTargets, - }); - - // Build a set of desired icon IDs - const desiredIconIds = new Set(icons.map((icon) => icon.id)); - - // Remove any icons that are no longer needed - for (const [id, element] of render.icons) { - if (!desiredIconIds.has(id)) { - if (id === ALLIANCE_ICON_ID) { - render.allianceIconRefs?.wrapper.remove(); - render.allianceIconRefs = null; - render.icons.delete(ALLIANCE_ICON_ID); - } else { - element.remove(); - render.icons.delete(id); - } - } - } - - // Add or update icons that should be shown - for (const icon of icons) { - if (icon.kind === EMOJI_ICON_KIND && icon.text) { - this.handleEmojiIcon(render, icon, iconSize); - continue; - } else if (!(icon.kind === IMAGE_ICON_KIND && icon.src)) { - continue; - } - // Special handling for alliance icon with progress indicator - if (icon.id === ALLIANCE_ICON_ID) { - this.handleAllianceIcons(render, iconSize, darkModeStr); - continue; // Skip regular image handling - } - - const imgElement = this.handleOtherIcons( - render, - icon, - iconSize, - darkModeStr, - ); - - // Traitor flashing - smooth speed increase starting at 15s - if (icon.id === TRAITOR_ICON_ID) { - this.handleTraitorIconFlashing(render.player, imgElement); - } - } - - // Position element with scale - // Don't require nameLocation to be changed: Scale update otherwise sometimes only happens after seconds which looks buggy. - // Because of sometimes overlapping delays of 20 ticks for nameLocation() (largestClusterBoundingBox in PlayerExecution) - // and the 500ms renderRefreshRate in here. - const scale = Math.min(baseSize * 0.25, 3); - const transform = `translate(${newX}px, ${newY}px) translate(-50%, -50%) scale(${scale})`; - if (render.lastTransform !== transform) { - render.element.style.transform = transform; - render.lastTransform = transform; - } - } - - private handleEmojiIcon( - render: RenderInfo, - icon: PlayerIconDescriptor, - size: number, - ) { - let emojiDiv = render.icons.get(icon.id) as HTMLDivElement | undefined; - - if (!emojiDiv) { - emojiDiv = this.emojiTemplate.cloneNode(true) as HTMLDivElement; - render.iconsDiv.appendChild(emojiDiv); - render.icons.set(icon.id, emojiDiv); - } - - emojiDiv.textContent = icon.text ?? ""; - emojiDiv.style.fontSize = `${size}px`; - } - - private handleAllianceIcons( - render: RenderInfo, - size: number, - darkMode: string, - ) { - this.myPlayer ??= this.game.myPlayer(); - const allianceView = this.myPlayer - ?.alliances() - .find((a) => a.other === render.player.id()); - - let fraction = 0; - let hasExtensionRequest = false; - if (allianceView) { - const remaining = Math.max(0, allianceView.expiresAt - this.game.ticks()); - fraction = Math.max(0, Math.min(1, remaining / this.allianceDuration)); - hasExtensionRequest = allianceView.hasExtensionRequest; - } - - if (!render.allianceIconRefs) { - render.allianceIconRefs = createAllianceProgressIconRefs( - size, - fraction, - hasExtensionRequest, - darkMode, - ); - - render.iconsDiv.appendChild(render.allianceIconRefs.wrapper); - render.icons.set(ALLIANCE_ICON_ID, render.allianceIconRefs.wrapper); - } else { - updateAllianceProgressIconRefs( - render.allianceIconRefs, - size, - fraction, - hasExtensionRequest, - darkMode, - ); - } - return; - } - - private handleOtherIcons( - render: RenderInfo, - icon: PlayerIconDescriptor, - size: number, - darkMode: string, - ): HTMLImageElement { - let imgElement = render.icons.get(icon.id) as HTMLImageElement | undefined; - - if (!imgElement) { - imgElement = icon.center - ? (this.iconCenterTemplate.cloneNode(true) as HTMLImageElement) - : (this.iconTemplate.cloneNode(true) as HTMLImageElement); - - imgElement.src = icon.src ?? ""; - imgElement.style.width = `${size}px`; - imgElement.style.height = `${size}px`; - imgElement.setAttribute("dark-mode", darkMode); - render.iconsDiv.appendChild(imgElement); - render.icons.set(icon.id, imgElement); - } else { - // Update src if it changed (e.g., nuke red/white or dark-mode icons) - if (imgElement.src !== icon.src) { - imgElement.src = icon.src ?? ""; - } - - imgElement.style.width = `${size}px`; - imgElement.style.height = `${size}px`; - imgElement.setAttribute("dark-mode", darkMode); - } - return imgElement; - } - - private handleTraitorIconFlashing( - player: PlayerView, - icon: HTMLImageElement, - ) { - const remainingTicks = player.getTraitorRemainingTicks(); - // Use precise seconds (not rounded) for smoother transitions, rounded to 0.5s intervals - const remainingSeconds = Math.round((remainingTicks / 10) * 2) / 2; - - if (remainingSeconds <= 15) { - // Smooth transition: starts at 1s at 15 seconds, decreases to 0.2s at 0 seconds - // Using cubic ease-out for slower, more gradual acceleration - const clampedSeconds = Math.max(0, Math.min(15, remainingSeconds)); - const normalizedTime = clampedSeconds / 15; // 0 to 1 (1 = 15s remaining, 0 = 0s remaining) - - // Cubic ease-out: slower acceleration, smoother transition - const easedProgress = 1 - Math.pow(1 - normalizedTime, 3); - const maxDuration = 1.0; // Slow flash at 15 seconds - const minDuration = 0.2; // Fast flash at 0 seconds - const duration = - minDuration + (maxDuration - minDuration) * easedProgress; - const animationDuration = `${duration.toFixed(2)}s`; - - icon.style.animation = `traitorFlash ${animationDuration} infinite`; - icon.style.animationTimingFunction = "ease-in-out"; - } else { - // Don't flash if more than 15 seconds remaining - icon.style.animation = "none"; - } - } -} From 45246f2085d2ebf1bd3baab0d75ead77536d7cec Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 19:21:49 -0700 Subject: [PATCH 11/34] make computePlayerStatus live-aware so status icons render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The replay-path computePlayerStatus left alliance/target/embargo/ nukeTargetsMe at false, which meant the WebGL NamePass had no data for those status icons after we switched names off canvas2D — they just stopped appearing. Add an opts param taking localPlayerID + tileState. When localPlayerID is set, fill the relative flags by checking the local player's allies/targets/embargoes against each other player's smallID; embargo is bilateral (either side). nukeTargetsMe walks active nukes and checks their targetTile's owner via the tileState buffer. Plumb localPlayerID = myPlayer?.smallID() and tileState from populateFrame so the live path uses the new mode. Emit an entry when only a relative flag is true (previously could be dropped if no base flag was set). allianceReq and allianceFraction stay deferred (need local PlayerID string for outgoing requests and current tick for fraction). 18 new tests covering both modes — replay (relative flags forced false), and live (alliance one-way, target one-way, embargo bilateral, self-flags suppressed, nukeTargetsMe with/without tileState, relative-flag-alone emits, localPlayerID=0 falls back to replay, allianceReq/allianceFraction stay deferred). --- .../render/frame/derive/player-status.ts | 99 +++++- src/client/view/GameView.ts | 5 +- .../render/frame/derive/player-status.test.ts | 334 ++++++++++++++++++ 3 files changed, 421 insertions(+), 17 deletions(-) create mode 100644 tests/client/render/frame/derive/player-status.test.ts diff --git a/src/client/render/frame/derive/player-status.ts b/src/client/render/frame/derive/player-status.ts index 2a165e007f..ec98808bcc 100644 --- a/src/client/render/frame/derive/player-status.ts +++ b/src/client/render/frame/derive/player-status.ts @@ -7,29 +7,70 @@ const NUKE_ACTIVE_TYPES: ReadonlySet = new Set([ UT_MIRV_WARHEAD, ]); +const OWNER_MASK = 0xfff; + +export interface ComputePlayerStatusOptions { + /** + * Local player smallID for computing relative flags. Omit (or set to 0) + * for replay mode — relative flags will all be false. + */ + localPlayerID?: number; + /** + * Tile state buffer (the same Uint16Array exposed via FrameData.tileState). + * Used to determine if a nuke's target tile is owned by the local player + * for the `nukeTargetsMe` flag. If omitted, `nukeTargetsMe` stays false. + */ + tileState?: Uint16Array; +} + /** * Compute per-player status flags for the name/status-icon pass. * - * This is the replay-path version — no local player concept. - * All relative flags (alliance, allianceReq, target, embargo, nukeTargetsMe) - * are always false. The live path uses the shim's own computePlayerStatus - * which has local-player awareness. + * Without `opts.localPlayerID`: replay-path mode. Crown/traitor/disconnected/ + * nukeActive are populated; relative flags (alliance/target/embargo/ + * nukeTargetsMe) are all false. + * + * With `opts.localPlayerID`: live mode. Relative flags compare each player + * against the local player's state to determine alliance/target/embargo; + * if `opts.tileState` is also given, `nukeTargetsMe` is set for players + * whose in-flight nuke is targeting one of the local player's tiles. + * + * `allianceReq` and `allianceFraction` are not computed yet — they need + * additional context (the local player's PlayerID string for outgoing + * requests, and the current tick for fraction). Left as `false`/`0` until + * those use cases need them. */ export function computePlayerStatus( players: ReadonlyMap, units: ReadonlyMap, + opts: ComputePlayerStatusOptions = {}, ): Map { const result = new Map(); + const localPlayerID = opts.localPlayerID ?? 0; + const tileState = opts.tileState; + const localPlayer = + localPlayerID > 0 ? players.get(localPlayerID) : undefined; - // Nuke owners: players who have an active nuke in flight + // Nuke owners: players who have an active nuke in flight. + // Also collect which of those nukes target a tile owned by the local player. const nukeOwners = new Set(); + const nukeAimedAtMe = new Set(); for (const u of units.values()) { - if (u.isActive && NUKE_ACTIVE_TYPES.has(u.unitType)) { - nukeOwners.add(u.ownerID); + if (!u.isActive || !NUKE_ACTIVE_TYPES.has(u.unitType)) continue; + nukeOwners.add(u.ownerID); + if ( + localPlayer !== undefined && + tileState !== undefined && + u.targetTile !== null + ) { + const tileOwner = tileState[u.targetTile] & OWNER_MASK; + if (tileOwner === localPlayerID) { + nukeAimedAtMe.add(u.ownerID); + } } } - // Crown: alive player with most tiles + // Crown: alive player with most tiles owned. let crownSmallID = -1; let maxTiles = 0; for (const ps of players.values()) { @@ -40,31 +81,57 @@ export function computePlayerStatus( } } + // Relative-flag sets seeded from the local player's state. Looking them + // up once outside the per-player loop is O(1) per player rather than O(n) + // per .includes(); doesn't matter at small scale but keeps the loop tidy. + const allySet = localPlayer ? new Set(localPlayer.allies) : null; + const targetSet = localPlayer ? new Set(localPlayer.targets) : null; + const myEmbargoes = localPlayer ? new Set(localPlayer.embargoes) : null; + for (const ps of players.values()) { if (!ps.isAlive) continue; - const crown = ps.smallID === crownSmallID; + const sid = ps.smallID; + const crown = sid === crownSmallID; const traitor = ps.isTraitor; const disconnected = ps.isDisconnected; const traitorRemainingTicks = ps.traitorRemainingTicks; - const nukeActive = nukeOwners.has(ps.smallID); + const nukeActive = nukeOwners.has(sid); + + // Relative flags — only meaningful when there's a local player AND we're + // not looking at the local player itself. + let alliance = false; + let target = false; + let embargo = false; + let nukeTargetsMe = false; + if (localPlayer !== undefined && sid !== localPlayerID) { + alliance = allySet!.has(sid); + target = targetSet!.has(sid); + // Embargo is bilateral: either side embargoes the other. + embargo = myEmbargoes!.has(sid) || ps.embargoes.includes(localPlayerID); + nukeTargetsMe = nukeAimedAtMe.has(sid); + } if ( crown || traitor || disconnected || traitorRemainingTicks > 0 || - nukeActive + nukeActive || + alliance || + target || + embargo || + nukeTargetsMe ) { - result.set(ps.smallID, { + result.set(sid, { crown, traitor, disconnected, - alliance: false, + alliance, allianceReq: false, - target: false, - embargo: false, + target, + embargo, nukeActive, - nukeTargetsMe: false, + nukeTargetsMe, traitorRemainingTicks, allianceFraction: 0, }); diff --git a/src/client/view/GameView.ts b/src/client/view/GameView.ts index 497ad7463d..4daf46d8ba 100644 --- a/src/client/view/GameView.ts +++ b/src/client/view/GameView.ts @@ -465,7 +465,10 @@ export class GameView implements GameMap { f.railroadDirty = this.railroadCache.railroadDirty; f.trailDirtyRowMin = this.trailManager.dirtyRowMin; f.trailDirtyRowMax = this.trailManager.dirtyRowMax; - f.playerStatus = computePlayerStatus(this._playerStates, this._unitStates); + f.playerStatus = computePlayerStatus(this._playerStates, this._unitStates, { + localPlayerID: this._myPlayer?.smallID() ?? 0, + tileState: this._map.tileStateBuffer(), + }); const rel = buildRelationMatrix(this._playerStates); f.relationMatrix = rel.matrix; f.relationSize = rel.size; diff --git a/tests/client/render/frame/derive/player-status.test.ts b/tests/client/render/frame/derive/player-status.test.ts new file mode 100644 index 0000000000..e511555b9b --- /dev/null +++ b/tests/client/render/frame/derive/player-status.test.ts @@ -0,0 +1,334 @@ +/** + * computePlayerStatus has two modes: + * + * - Replay mode (no localPlayerID): only crown / traitor / disconnected / + * nukeActive flags are populated. All relative flags are false. + * - Live mode (localPlayerID set): also fills alliance / target / embargo, + * and nukeTargetsMe if a tileState buffer is supplied. + * + * The function only emits an entry per player when at least one flag is true + * (the NamePass treats missing entries as "all flags off"). Tests assert + * both presence and absence of entries. + */ + +import { describe, expect, it } from "vitest"; +import { computePlayerStatus } from "../../../../../src/client/render/frame/derive/player-status"; +import type { + PlayerState, + UnitState, +} from "../../../../../src/client/render/types"; +import { + UT_ATOM_BOMB, + UT_WARSHIP, +} from "../../../../../src/client/render/types"; + +function ps(overrides: Partial = {}): PlayerState { + return { + smallID: 1, + isAlive: true, + isDisconnected: false, + tilesOwned: 0, + gold: 0, + troops: 0, + isTraitor: false, + traitorRemainingTicks: 0, + betrayals: 0, + hasSpawned: true, + lastDeleteUnitTick: 0, + allies: [], + embargoes: [], + targets: [], + outgoingAttacks: [], + incomingAttacks: [], + outgoingAllianceRequests: [], + alliances: [], + outgoingEmojis: [], + ...overrides, + }; +} + +function unit(overrides: Partial = {}): UnitState { + return { + id: 1, + unitType: UT_WARSHIP, + ownerID: 1, + lastOwnerID: null, + pos: 0, + lastPos: 0, + isActive: true, + reachedTarget: false, + retreating: false, + targetable: true, + markedForDeletion: false, + health: null, + underConstruction: false, + targetUnitId: null, + targetTile: null, + troops: 0, + missileTimerQueue: [], + level: 1, + hasTrainStation: false, + trainType: null, + loaded: null, + constructionStartTick: null, + ...overrides, + }; +} + +function playersMap(...players: PlayerState[]): Map { + return new Map(players.map((p) => [p.smallID, p])); +} + +function unitsMap(...us: UnitState[]): Map { + return new Map(us.map((u) => [u.id, u])); +} + +describe("computePlayerStatus — replay mode (no localPlayerID)", () => { + it("returns empty map when no flags are set", () => { + const players = playersMap(ps({ smallID: 1 })); + const status = computePlayerStatus(players, unitsMap()); + expect(status.size).toBe(0); + }); + + it("crown goes to the alive player with most tiles owned", () => { + const players = playersMap( + ps({ smallID: 1, tilesOwned: 100 }), + ps({ smallID: 2, tilesOwned: 500 }), // king + ps({ smallID: 3, tilesOwned: 250 }), + ); + const status = computePlayerStatus(players, unitsMap()); + expect(status.get(2)?.crown).toBe(true); + // Players 1 and 3 don't have crown and no other flags → no entry emitted. + expect(status.has(1)).toBe(false); + expect(status.has(3)).toBe(false); + }); + + it("dead players don't get the crown even if they had the most tiles", () => { + const players = playersMap( + ps({ smallID: 1, tilesOwned: 1000, isAlive: false }), + ps({ smallID: 2, tilesOwned: 100 }), + ); + const status = computePlayerStatus(players, unitsMap()); + expect(status.get(2)?.crown).toBe(true); + expect(status.has(1)).toBe(false); + }); + + it("traitor + traitorRemainingTicks flow through", () => { + const players = playersMap( + ps({ smallID: 1, isTraitor: true, traitorRemainingTicks: 42 }), + ); + const status = computePlayerStatus(players, unitsMap()); + expect(status.get(1)?.traitor).toBe(true); + expect(status.get(1)?.traitorRemainingTicks).toBe(42); + }); + + it("disconnected flag flows through", () => { + const players = playersMap(ps({ smallID: 1, isDisconnected: true })); + const status = computePlayerStatus(players, unitsMap()); + expect(status.get(1)?.disconnected).toBe(true); + }); + + it("nukeActive: any in-flight nuke marks its owner", () => { + const players = playersMap(ps({ smallID: 1 }), ps({ smallID: 2 })); + const units = unitsMap( + unit({ id: 10, ownerID: 2, unitType: UT_ATOM_BOMB, isActive: true }), + ); + const status = computePlayerStatus(players, units); + expect(status.get(2)?.nukeActive).toBe(true); + expect(status.has(1)).toBe(false); + }); + + it("inactive nukes don't trigger nukeActive", () => { + const players = playersMap(ps({ smallID: 1 })); + const units = unitsMap( + unit({ id: 10, ownerID: 1, unitType: UT_ATOM_BOMB, isActive: false }), + ); + const status = computePlayerStatus(players, units); + expect(status.has(1)).toBe(false); + }); + + it("relative flags (alliance/target/embargo/nukeTargetsMe) are always false in replay mode", () => { + const players = playersMap( + ps({ smallID: 1, allies: [2], targets: [2], embargoes: [2] }), + ps({ smallID: 2, tilesOwned: 1 }), // crown so an entry exists + ); + const status = computePlayerStatus(players, unitsMap()); + expect(status.get(2)?.alliance).toBe(false); + expect(status.get(2)?.target).toBe(false); + expect(status.get(2)?.embargo).toBe(false); + expect(status.get(2)?.nukeTargetsMe).toBe(false); + }); +}); + +describe("computePlayerStatus — live mode (localPlayerID set)", () => { + it("alliance: local has them as ally → alliance true", () => { + const players = playersMap( + ps({ smallID: 1, allies: [2] }), // me + ps({ smallID: 2 }), + ); + const status = computePlayerStatus(players, unitsMap(), { + localPlayerID: 1, + }); + expect(status.get(2)?.alliance).toBe(true); + }); + + it("target: local has them in targets → target true", () => { + const players = playersMap( + ps({ smallID: 1, targets: [2] }), // me + ps({ smallID: 2 }), + ); + const status = computePlayerStatus(players, unitsMap(), { + localPlayerID: 1, + }); + expect(status.get(2)?.target).toBe(true); + }); + + it("embargo is bilateral: true if I embargo them OR they embargo me", () => { + // I embargo them. + let status = computePlayerStatus( + playersMap(ps({ smallID: 1, embargoes: [2] }), ps({ smallID: 2 })), + unitsMap(), + { localPlayerID: 1 }, + ); + expect(status.get(2)?.embargo).toBe(true); + + // They embargo me. + status = computePlayerStatus( + playersMap(ps({ smallID: 1 }), ps({ smallID: 2, embargoes: [1] })), + unitsMap(), + { localPlayerID: 1 }, + ); + expect(status.get(2)?.embargo).toBe(true); + + // Neither. + status = computePlayerStatus( + playersMap(ps({ smallID: 1 }), ps({ smallID: 2, tilesOwned: 1 })), + unitsMap(), + { localPlayerID: 1 }, + ); + // Player 2 only has crown — embargo should be false. + expect(status.get(2)?.embargo).toBe(false); + }); + + it("relative flags are NOT set for the local player itself (no self-relationships)", () => { + const players = playersMap( + ps({ + smallID: 1, + tilesOwned: 100, + allies: [1], + targets: [1], + embargoes: [1], + }), + ps({ smallID: 2 }), + ); + const status = computePlayerStatus(players, unitsMap(), { + localPlayerID: 1, + }); + // Player 1 (local) gets crown but no relative flags vs. self. + expect(status.get(1)?.crown).toBe(true); + expect(status.get(1)?.alliance).toBe(false); + expect(status.get(1)?.target).toBe(false); + expect(status.get(1)?.embargo).toBe(false); + }); + + it("nukeTargetsMe: requires tileState — without it, stays false", () => { + const players = playersMap(ps({ smallID: 1 }), ps({ smallID: 2 })); + const units = unitsMap( + unit({ + id: 10, + ownerID: 2, + unitType: UT_ATOM_BOMB, + isActive: true, + targetTile: 5, + }), + ); + const status = computePlayerStatus(players, units, { localPlayerID: 1 }); + expect(status.get(2)?.nukeActive).toBe(true); + expect(status.get(2)?.nukeTargetsMe).toBe(false); + }); + + it("nukeTargetsMe: true when nuke targets a tile owned by local player", () => { + const players = playersMap(ps({ smallID: 1 }), ps({ smallID: 2 })); + const units = unitsMap( + unit({ + id: 10, + ownerID: 2, + unitType: UT_ATOM_BOMB, + isActive: true, + targetTile: 5, + }), + ); + // tileState[5] low 12 bits = 1 (local player's smallID). + const tileState = new Uint16Array(16); + tileState[5] = 1; + + const status = computePlayerStatus(players, units, { + localPlayerID: 1, + tileState, + }); + expect(status.get(2)?.nukeTargetsMe).toBe(true); + }); + + it("nukeTargetsMe: false when nuke targets a tile owned by someone else", () => { + const players = playersMap( + ps({ smallID: 1 }), + ps({ smallID: 2 }), + ps({ smallID: 3 }), + ); + const units = unitsMap( + unit({ + id: 10, + ownerID: 2, + unitType: UT_ATOM_BOMB, + isActive: true, + targetTile: 5, + }), + ); + // tileState[5] = player 3, not me. + const tileState = new Uint16Array(16); + tileState[5] = 3; + + const status = computePlayerStatus(players, units, { + localPlayerID: 1, + tileState, + }); + expect(status.get(2)?.nukeTargetsMe).toBe(false); + }); + + it("entry is emitted when only a relative flag is true (even with no base flags)", () => { + const players = playersMap( + ps({ smallID: 1, allies: [2] }), // me + ps({ smallID: 2 }), // no other flags + ); + const status = computePlayerStatus(players, unitsMap(), { + localPlayerID: 1, + }); + // Without local-mode, player 2 wouldn't get an entry — alliance is the + // only reason it shows up here. + expect(status.get(2)).toBeDefined(); + expect(status.get(2)?.alliance).toBe(true); + }); + + it("localPlayerID = 0 (no local player) behaves like replay mode", () => { + const players = playersMap( + ps({ smallID: 1, allies: [2] }), + ps({ smallID: 2, tilesOwned: 1 }), + ); + const status = computePlayerStatus(players, unitsMap(), { + localPlayerID: 0, + }); + expect(status.get(2)?.alliance).toBe(false); + }); + + it("allianceReq and allianceFraction are not computed (deferred)", () => { + const players = playersMap( + ps({ smallID: 1, allies: [2] }), + ps({ smallID: 2 }), + ); + const status = computePlayerStatus(players, unitsMap(), { + localPlayerID: 1, + }); + expect(status.get(2)?.allianceReq).toBe(false); + expect(status.get(2)?.allianceFraction).toBe(0); + }); +}); From d1651017eacf1d2843abe2ac47bfb485b0269ee3 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 19:37:55 -0700 Subject: [PATCH 12/34] migrate warship drag rectangle from canvas2D to DOM overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shift+drag warship selection rectangle was drawn on a second offscreen canvas, blitted onto the main canvas2D context every frame via world-coord transform. It's a screen-space rectangle though, so none of that math was load-bearing. Replace with a `
    ` positioned via inline left/top/width/height in screen pixels. Same color tinting (player territoryColor lightened 0.2, dashed border at 0.85 alpha, fill at 0.06). pointer-events:none so it doesn't intercept the drag. Drops ~95 LOC of canvas2D drawing (renderSelectionBox, drawDashedLine, selectionBoxCanvas/Ctx, the redraw() init, the renderLayer() blit). One step closer to retiring the canvas2D map canvas — UILayer's per-unit selection outlines are the last canvas2D draws on it. --- src/client/graphics/layers/UILayer.ts | 174 ++++++++++---------------- 1 file changed, 63 insertions(+), 111 deletions(-) diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 0b1aafe366..3e28887812 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -54,15 +54,9 @@ export class UILayer implements Layer { // Visual settings for selection private readonly SELECTION_BOX_SIZE = 6; // Size of the selection box (should be larger than the warship) - // Selection box (drag rectangle) state - private selectionBoxActive = false; - private selectionBoxStartX = 0; - private selectionBoxStartY = 0; - private selectionBoxEndX = 0; - private selectionBoxEndY = 0; - private selectionBoxCanvas: HTMLCanvasElement = - document.createElement("canvas"); - private selectionBoxCtx: CanvasRenderingContext2D | null = null; + // Drag rectangle (shift+drag warship selection box) — a screen-space DOM + // overlay positioned via inline style. Not part of the canvas2D draw path. + private dragRectEl: HTMLDivElement | null = null; constructor( private game: GameView, @@ -106,22 +100,12 @@ export class UILayer implements Layer { init() { this.eventBus.on(UnitSelectionEvent, (e) => this.onUnitSelection(e)); + + this.ensureDragRectEl(); this.eventBus.on(WarshipSelectionBoxUpdateEvent, (e) => { - this.selectionBoxActive = true; - this.selectionBoxStartX = e.startX; - this.selectionBoxStartY = e.startY; - this.selectionBoxEndX = e.endX; - this.selectionBoxEndY = e.endY; + this.updateDragRect(e.startX, e.startY, e.endX, e.endY); }); - const clearBox = () => { - this.selectionBoxActive = false; - this.selectionBoxCtx?.clearRect( - 0, - 0, - this.selectionBoxCanvas.width, - this.selectionBoxCanvas.height, - ); - }; + const clearBox = () => this.hideDragRect(); this.eventBus.on(WarshipSelectionBoxCompleteEvent, clearBox); this.eventBus.on(WarshipSelectionBoxCancelEvent, clearBox); this.eventBus.on(CloseViewEvent, clearBox); @@ -137,6 +121,62 @@ export class UILayer implements Layer { this.redraw(); } + /** + * Lazily create the shift+drag rectangle overlay. Screen-space DOM element, + * pointer-events: none so it doesn't intercept the drag itself. z-index + * sits above the WebGL/canvas2D map canvases but below HUD modals. + */ + private ensureDragRectEl(): void { + if (this.dragRectEl !== null) return; + const el = document.createElement("div"); + el.id = "warship-drag-rect"; + el.style.position = "fixed"; + el.style.pointerEvents = "none"; + el.style.display = "none"; + el.style.zIndex = "30"; + el.style.borderStyle = "dashed"; + el.style.borderWidth = "1px"; + el.style.boxSizing = "border-box"; + document.body.appendChild(el); + this.dragRectEl = el; + } + + private updateDragRect( + startX: number, + startY: number, + endX: number, + endY: number, + ): void { + const el = this.dragRectEl; + if (el === null) return; + const x1 = Math.min(startX, endX); + const y1 = Math.min(startY, endY); + const w = Math.abs(endX - startX); + const h = Math.abs(endY - startY); + + // Color from the local player's territory tint (matches the canvas2D look). + const myPlayer = this.game.myPlayer(); + const base = myPlayer ? myPlayer.territoryColor().lighten(0.2) : null; + const border = base + ? base.alpha(0.85).toRgbString() + : "rgba(100, 200, 255, 0.85)"; + const fill = base + ? base.alpha(0.06).toRgbString() + : "rgba(100, 200, 255, 0.06)"; + + el.style.left = `${x1}px`; + el.style.top = `${y1}px`; + el.style.width = `${w}px`; + el.style.height = `${h}px`; + el.style.borderColor = border; + el.style.backgroundColor = fill; + el.style.display = "block"; + } + + private hideDragRect(): void { + if (this.dragRectEl !== null) this.dragRectEl.style.display = "none"; + } + /** * Find player-owned warships near the given cell, sorted by distance. */ @@ -298,89 +338,6 @@ export class UILayer implements Layer { this.game.width(), this.game.height(), ); - if (this.selectionBoxActive) { - this.renderSelectionBox(context); - } - } - - private renderSelectionBox(context: CanvasRenderingContext2D) { - if (!this.selectionBoxCtx) return; - - const topLeft = this.transformHandler.screenToWorldCoordinates( - Math.min(this.selectionBoxStartX, this.selectionBoxEndX), - Math.min(this.selectionBoxStartY, this.selectionBoxEndY), - ); - const bottomRight = this.transformHandler.screenToWorldCoordinates( - Math.max(this.selectionBoxStartX, this.selectionBoxEndX), - Math.max(this.selectionBoxStartY, this.selectionBoxEndY), - ); - - const cx1 = Math.max(0, Math.floor(topLeft.x)); - const cy1 = Math.max(0, Math.floor(topLeft.y)); - const cx2 = Math.min( - this.selectionBoxCanvas.width - 1, - Math.floor(bottomRight.x), - ); - const cy2 = Math.min( - this.selectionBoxCanvas.height - 1, - Math.floor(bottomRight.y), - ); - - if (cx2 <= cx1 || cy2 <= cy1) return; - - const myPlayer = this.game.myPlayer(); - const baseColor = myPlayer ? myPlayer.territoryColor().lighten(0.2) : null; - const colorStr = baseColor - ? baseColor.alpha(0.85).toRgbString() - : "rgba(100,200,255,0.85)"; - - this.selectionBoxCtx.clearRect( - 0, - 0, - this.selectionBoxCanvas.width, - this.selectionBoxCanvas.height, - ); - this.selectionBoxCtx.fillStyle = colorStr; - this.drawDashedLine(this.selectionBoxCtx, cx1, cy1, cx2, cy1); - this.drawDashedLine(this.selectionBoxCtx, cx1, cy2, cx2, cy2); - this.drawDashedLine(this.selectionBoxCtx, cx1, cy1, cx1, cy2); - this.drawDashedLine(this.selectionBoxCtx, cx2, cy1, cx2, cy2); - - this.selectionBoxCtx.fillStyle = baseColor - ? baseColor.alpha(0.06).toRgbString() - : "rgba(100,200,255,0.06)"; - this.selectionBoxCtx.fillRect( - cx1 + 1, - cy1 + 1, - cx2 - cx1 - 1, - cy2 - cy1 - 1, - ); - - context.drawImage( - this.selectionBoxCanvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - } - - private drawDashedLine( - ctx: CanvasRenderingContext2D, - x1: number, - y1: number, - x2: number, - y2: number, - ) { - if (x1 === x2) { - for (let y = y1; y <= y2; y++) { - if ((x1 + y) % 2 === 0) ctx.fillRect(x1, y, 1, 1); - } - } else { - for (let x = x1; x <= x2; x++) { - if ((x + y1) % 2 === 0) ctx.fillRect(x, y1, 1, 1); - } - } } redraw() { @@ -388,11 +345,6 @@ export class UILayer implements Layer { this.context = this.canvas.getContext("2d"); this.canvas.width = this.game.width(); this.canvas.height = this.game.height(); - - this.selectionBoxCanvas = document.createElement("canvas"); - this.selectionBoxCanvas.width = this.game.width(); - this.selectionBoxCanvas.height = this.game.height(); - this.selectionBoxCtx = this.selectionBoxCanvas.getContext("2d"); } /** From ede0fb766804ce450d2289626d7d328cd757f9c5 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 19:53:13 -0700 Subject: [PATCH 13/34] move single-unit warship selection box to WebGL SelectionBoxPass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UnitSelectionEvent now forwards to view.setSelectedUnit(unit.id()) in mountWebGLDebugRenderer; the renderer's SelectionBoxPass draws the animated stippled outline on the GPU. UILayer still tracks selectedUnit for game-logic readers (the click handlers) but no longer paints to canvas2D for it. Drops drawSelectionBox + lastSelectionBoxCenter (~50 LOC) plus the per-tick single-unit redraw in tick(). Multi-selection stays on canvas2D — SelectionBoxPass is single-unit only. Test update: replaces the now-dead drawSelectionBox spy with a selectedUnit state assertion + a deselect case. --- src/client/ClientGameRunner.ts | 18 +++++ src/client/graphics/layers/UILayer.ts | 111 +++++--------------------- tests/client/graphics/UILayer.test.ts | 25 +++++- 3 files changed, 62 insertions(+), 92 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 09047f3717..93de7a16e8 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -43,6 +43,7 @@ import { MouseMoveEvent, MouseUpEvent, TickMetricsEvent, + UnitSelectionEvent, } from "./InputHandler"; import { endGame, startGame, startTime } from "./LocalPersistantStats"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; @@ -345,6 +346,23 @@ function mountWebGLDebugRenderer( view.showMoveIndicator(tx, ty, firstUnit.owner().smallID()); }); + // Single-unit warship selection box: forward UnitSelectionEvent to the + // renderer's SelectionBoxPass. Multi-selection (event.units.length > 0) + // stays canvas2D for now — SelectionBoxPass only supports one unit. + eventBus.on(UnitSelectionEvent, (e) => { + if (!e.isSelected) { + view.setSelectedUnit(null); + return; + } + if ((e.units ?? []).length > 0) { + // Multi-selection: drop any prior single highlight; canvas2D draws + // the multi outlines in UILayer. + view.setSelectedUnit(null); + return; + } + view.setSelectedUnit(e.unit?.id() ?? null); + }); + return { builder: new WebGLFrameBuilder(view), syncCamera }; } diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 3e28887812..3cb75baf16 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -44,13 +44,6 @@ export class UILayer implements Layer { { x: number; y: number; size: number } > = new Map(); - // Keep track of previous selection box position for cleanup - private lastSelectionBoxCenter: { - x: number; - y: number; - size: number; - } | null = null; - // Visual settings for selection private readonly SELECTION_BOX_SIZE = 6; // Size of the selection box (should be larger than the warship) @@ -71,14 +64,10 @@ export class UILayer implements Layer { } tick() { - // Update the selection animation time + // Update the selection animation time (only used by the multi-selection + // boxes — the single-unit box is now drawn by the WebGL SelectionBoxPass). this.selectionAnimTime = (this.selectionAnimTime + 1) % 60; - // If there's a selected warship, redraw to update the selection box animation - if (this.selectedUnit && this.selectedUnit.type() === UnitType.Warship) { - this.drawSelectionBox(this.selectedUnit); - } - // Animate multi-selected warships for (const unit of this.multiSelectedWarships) { if (unit.isActive()) { @@ -354,50 +343,29 @@ export class UILayer implements Layer { * When event.isSelected is false it clears all selection state. */ private onUnitSelection(event: UnitSelectionEvent) { - if (event.isSelected) { - // Always clear single-selection outline first - if (this.lastSelectionBoxCenter) { - const { x, y, size } = this.lastSelectionBoxCenter; - this.clearSelectionBox(x, y, size); - this.lastSelectionBoxCenter = null; - } - // selectedUnit is always reset regardless of lastSelectionBoxCenter - this.selectedUnit = null; - // Always clear previous multi-selection boxes - for (const [, center] of this.multiSelectionBoxCenters) { - this.clearSelectionBox(center.x, center.y, center.size); - } - this.multiSelectionBoxCenters.clear(); - this.multiSelectedWarships = []; - - if ((event.units ?? []).length > 0) { - // Multi-selection - this.multiSelectedWarships = event.units; - for (const unit of this.multiSelectedWarships) { - if (unit.isActive()) { - this.drawSelectionBoxMulti(unit); - } - } - } else { - // Single selection - this.selectedUnit = event.unit; - if (event.unit && event.unit.type() === UnitType.Warship) { - this.drawSelectionBox(event.unit); + // Clear previous multi-selection boxes (the single-unit box is now drawn + // by the WebGL SelectionBoxPass — see ClientGameRunner.mountWebGLDebugRenderer + // which forwards this event to view.setSelectedUnit). + for (const [, center] of this.multiSelectionBoxCenters) { + this.clearSelectionBox(center.x, center.y, center.size); + } + this.multiSelectionBoxCenters.clear(); + this.multiSelectedWarships = []; + this.selectedUnit = null; + + if (!event.isSelected) return; + + if ((event.units ?? []).length > 0) { + // Multi-selection — canvas2D draws the per-unit outlines. + this.multiSelectedWarships = event.units; + for (const unit of this.multiSelectedWarships) { + if (unit.isActive()) { + this.drawSelectionBoxMulti(unit); } } } else { - // Deselect everything - if (this.lastSelectionBoxCenter) { - const { x, y, size } = this.lastSelectionBoxCenter; - this.clearSelectionBox(x, y, size); - this.lastSelectionBoxCenter = null; - } - this.selectedUnit = null; - for (const [, center] of this.multiSelectionBoxCenters) { - this.clearSelectionBox(center.x, center.y, center.size); - } - this.multiSelectionBoxCenters.clear(); - this.multiSelectedWarships = []; + // Single selection — state only; WebGL draws the box. + this.selectedUnit = event.unit; } } @@ -471,41 +439,6 @@ export class UILayer implements Layer { } } - /** - * Draw a selection box around the given unit - */ - public drawSelectionBox(unit: UnitView) { - if (!unit || !unit.isActive()) { - return; - } - - if (this.theme === null) throw new Error("missing theme"); - const selectionColor = unit.owner().territoryColor().lighten(0.2); - const centerX = this.game.x(unit.tile()); - const centerY = this.game.y(unit.tile()); - - // Clear previous box if unit moved - if ( - this.lastSelectionBoxCenter && - (this.lastSelectionBoxCenter.x !== centerX || - this.lastSelectionBoxCenter.y !== centerY) - ) { - this.clearSelectionBox( - this.lastSelectionBoxCenter.x, - this.lastSelectionBoxCenter.y, - this.lastSelectionBoxCenter.size, - ); - } - - this.paintSelectionBoxAt(centerX, centerY, selectionColor); - - this.lastSelectionBoxCenter = { - x: centerX, - y: centerY, - size: this.SELECTION_BOX_SIZE, - }; - } - paintCell(x: number, y: number, color: Colord, alpha: number) { if (this.context === null) throw new Error("null context"); this.clearCell(x, y); diff --git a/tests/client/graphics/UILayer.test.ts b/tests/client/graphics/UILayer.test.ts index 7ada986990..63d0501a35 100644 --- a/tests/client/graphics/UILayer.test.ts +++ b/tests/client/graphics/UILayer.test.ts @@ -36,7 +36,7 @@ describe("UILayer", () => { expect(ui["context"]).not.toBeNull(); }); - it("should handle unit selection event", () => { + it("tracks the selected unit on single-unit selection (rendering is WebGL)", () => { const ui = new UILayer(game, eventBus, transformHandler); ui.redraw(); const unit = { @@ -46,8 +46,27 @@ describe("UILayer", () => { owner: () => ({}), }; const event = { isSelected: true, unit }; - ui.drawSelectionBox = vi.fn(); ui["onUnitSelection"](event as UnitSelectionEvent); - expect(ui.drawSelectionBox).toHaveBeenCalledWith(unit); + // selectedUnit is held for game-logic callers (the click handlers). The + // visual selection box is now drawn by WebGL SelectionBoxPass — wired + // from ClientGameRunner via view.setSelectedUnit(unit.id()). + expect(ui["selectedUnit"]).toBe(unit); + }); + + it("clears selection on deselect", () => { + const ui = new UILayer(game, eventBus, transformHandler); + ui.redraw(); + const unit = { + type: () => "Warship", + isActive: () => true, + tile: () => ({}), + owner: () => ({}), + }; + ui["onUnitSelection"]({ isSelected: true, unit } as UnitSelectionEvent); + ui["onUnitSelection"]({ + isSelected: false, + unit: null, + } as unknown as UnitSelectionEvent); + expect(ui["selectedUnit"]).toBeNull(); }); }); From 923cba8c2d106ddcd07e5a5d6a5556cbba749baa Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 20:02:31 -0700 Subject: [PATCH 14/34] move multi-unit warship selection box to WebGL SelectionBoxPass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SelectionBoxPass now stores an array of selections and renders one quad per entry. GPURenderer gains setSelectedUnits(ids) — the single-unit setSelectedUnit becomes a wrapper. Position + color are rebuilt each frame from lastUnits; dead unit IDs get pruned in place. ClientGameRunner's UnitSelectionEvent listener forwards both single and multi to view.setSelectedUnits — no more single/multi split. UILayer drops everything canvas2D-related: the offscreen canvas + context, theme, selectionAnimTime, multiSelectionBoxCenters, SELECTION_BOX_SIZE, drawSelectionBoxMulti, paintSelectionBoxAt, clearSelectionBox, paintCell, clearCell, and renderLayer / redraw / shouldTransform. tick() now only prunes destroyed warships from the selection list; the layer is purely state + click handling. ~120 LOC gone. Tests: UILayer.test.ts updated — drops the canvas/redraw asserts, adds a multi-selection state assertion. --- src/client/ClientGameRunner.ts | 16 +- src/client/graphics/layers/UILayer.ts | 179 ++---------------- src/client/render/gl/game-view.ts | 5 + .../render/gl/passes/selection-box-pass.ts | 62 +++--- src/client/render/gl/renderer.ts | 77 +++++--- tests/client/graphics/UILayer.test.ts | 29 +-- 6 files changed, 133 insertions(+), 235 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 93de7a16e8..d832989a8d 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -346,21 +346,19 @@ function mountWebGLDebugRenderer( view.showMoveIndicator(tx, ty, firstUnit.owner().smallID()); }); - // Single-unit warship selection box: forward UnitSelectionEvent to the - // renderer's SelectionBoxPass. Multi-selection (event.units.length > 0) - // stays canvas2D for now — SelectionBoxPass only supports one unit. + // Warship selection boxes: forward UnitSelectionEvent to the renderer's + // SelectionBoxPass for both single and multi selections. eventBus.on(UnitSelectionEvent, (e) => { if (!e.isSelected) { - view.setSelectedUnit(null); + view.setSelectedUnits([]); return; } - if ((e.units ?? []).length > 0) { - // Multi-selection: drop any prior single highlight; canvas2D draws - // the multi outlines in UILayer. - view.setSelectedUnit(null); + const multi = e.units ?? []; + if (multi.length > 0) { + view.setSelectedUnits(multi.map((u) => u.id())); return; } - view.setSelectedUnit(e.unit?.id() ?? null); + view.setSelectedUnits(e.unit ? [e.unit.id()] : []); }); return { builder: new WebGLFrameBuilder(view), syncCamera }; diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 3cb75baf16..d6072d02c0 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -1,5 +1,3 @@ -import { Colord } from "colord"; -import { Theme } from "src/core/configuration/Theme"; import { Cell } from "src/core/game/Game"; import { EventBus } from "../../../core/EventBus"; import { UnitType } from "../../../core/game/Game"; @@ -23,65 +21,35 @@ import { Layer } from "./Layer"; const WARSHIP_SELECTION_RADIUS = 10; /** - * Layer responsible for drawing UI elements that overlay the game. - * Currently: warship selection boxes + drag-rectangle selection. - * Health/progress bars are now drawn by the WebGL BarPass. + * Layer responsible for warship selection state + click handling. + * + * Drawing for selection boxes (single + multi) lives in the WebGL + * SelectionBoxPass; the drag-rectangle preview is a screen-space DOM + * overlay (dragRectEl). This layer does not draw to canvas2D at all — + * it stays in the Layer list for lifecycle hooks (init / tick / event + * subscriptions). */ export class UILayer implements Layer { - private canvas: HTMLCanvasElement; - private context: CanvasRenderingContext2D | null; - private theme: Theme | null = null; - private selectionAnimTime = 0; - // Keep track of currently selected unit + // Currently selected single warship (game-logic readers use this; the + // visual is drawn by WebGL SelectionBoxPass). private selectedUnit: UnitView | null = null; - - // Keep track of multi-selected warships (box selection) + // Currently multi-selected warships (shift+drag box select). private multiSelectedWarships: UnitView[] = []; - // Per-unit last selection box position for multi-select cleanup - private multiSelectionBoxCenters: Map< - number, - { x: number; y: number; size: number } - > = new Map(); - - // Visual settings for selection - private readonly SELECTION_BOX_SIZE = 6; // Size of the selection box (should be larger than the warship) - // Drag rectangle (shift+drag warship selection box) — a screen-space DOM - // overlay positioned via inline style. Not part of the canvas2D draw path. + // overlay positioned via inline style. private dragRectEl: HTMLDivElement | null = null; constructor( private game: GameView, private eventBus: EventBus, private transformHandler: TransformHandler, - ) { - this.theme = game.config().theme(); - } - - shouldTransform(): boolean { - return true; - } + ) {} tick() { - // Update the selection animation time (only used by the multi-selection - // boxes — the single-unit box is now drawn by the WebGL SelectionBoxPass). - this.selectionAnimTime = (this.selectionAnimTime + 1) % 60; - - // Animate multi-selected warships - for (const unit of this.multiSelectedWarships) { - if (unit.isActive()) { - this.drawSelectionBoxMulti(unit); - } else { - // Unit was destroyed — clean up its box - const prev = this.multiSelectionBoxCenters.get(unit.id()); - if (prev) { - this.clearSelectionBox(prev.x, prev.y, prev.size); - this.multiSelectionBoxCenters.delete(unit.id()); - } - } - } - // Remove destroyed units from the list + // Prune any destroyed warships from the multi-selection so callers + // (move-warship intent) don't try to act on dead units. The WebGL + // SelectionBoxPass also drops them automatically. this.multiSelectedWarships = this.multiSelectedWarships.filter((u) => u.isActive(), ); @@ -106,8 +74,6 @@ export class UILayer implements Layer { this.onSelectionBoxComplete(e), ); this.eventBus.on(SelectAllWarshipsEvent, () => this.onSelectAllWarships()); - - this.redraw(); } /** @@ -319,23 +285,6 @@ export class UILayer implements Layer { this.eventBus.emit(new UnitSelectionEvent(null, true, allWarships)); } - renderLayer(context: CanvasRenderingContext2D) { - context.drawImage( - this.canvas, - -this.game.width() / 2, - -this.game.height() / 2, - this.game.width(), - this.game.height(), - ); - } - - redraw() { - this.canvas = document.createElement("canvas"); - this.context = this.canvas.getContext("2d"); - this.canvas.width = this.game.width(); - this.canvas.height = this.game.height(); - } - /** * Handle the unit selection event (single or multi). * When event.units.length > 0 it's a multi-selection from box/select-all. @@ -343,111 +292,17 @@ export class UILayer implements Layer { * When event.isSelected is false it clears all selection state. */ private onUnitSelection(event: UnitSelectionEvent) { - // Clear previous multi-selection boxes (the single-unit box is now drawn - // by the WebGL SelectionBoxPass — see ClientGameRunner.mountWebGLDebugRenderer - // which forwards this event to view.setSelectedUnit). - for (const [, center] of this.multiSelectionBoxCenters) { - this.clearSelectionBox(center.x, center.y, center.size); - } - this.multiSelectionBoxCenters.clear(); + // Selection box visuals are drawn by the WebGL SelectionBoxPass; this + // method just tracks selection state for the click-handler logic. this.multiSelectedWarships = []; this.selectedUnit = null; if (!event.isSelected) return; if ((event.units ?? []).length > 0) { - // Multi-selection — canvas2D draws the per-unit outlines. this.multiSelectedWarships = event.units; - for (const unit of this.multiSelectedWarships) { - if (unit.isActive()) { - this.drawSelectionBoxMulti(unit); - } - } } else { - // Single selection — state only; WebGL draws the box. this.selectedUnit = event.unit; } } - - /** - * Draw selection box for a multi-selected warship, tracking position per unit id. - */ - private drawSelectionBoxMulti(unit: UnitView) { - if (!unit || !unit.isActive()) return; - - if (this.theme === null) throw new Error("missing theme"); - const selectionColor = unit.owner().territoryColor().lighten(0.2); - const centerX = this.game.x(unit.tile()); - const centerY = this.game.y(unit.tile()); - - const prev = this.multiSelectionBoxCenters.get(unit.id()); - if (prev && (prev.x !== centerX || prev.y !== centerY)) { - this.clearSelectionBox(prev.x, prev.y, prev.size); - } - - this.paintSelectionBoxAt(centerX, centerY, selectionColor); - - this.multiSelectionBoxCenters.set(unit.id(), { - x: centerX, - y: centerY, - size: this.SELECTION_BOX_SIZE, - }); - } - - /** - * Shared helper: paint the dashed pulsing border pixels for a selection box. - */ - private paintSelectionBoxAt( - centerX: number, - centerY: number, - selectionColor: Colord, - ) { - const size = this.SELECTION_BOX_SIZE; - const opacity = 200 + Math.sin(this.selectionAnimTime * 0.1) * 55; - - for (let x = centerX - size; x <= centerX + size; x++) { - for (let y = centerY - size; y <= centerY + size; y++) { - if ( - x === centerX - size || - x === centerX + size || - y === centerY - size || - y === centerY + size - ) { - if ((x + y) % 2 === 0) { - this.paintCell(x, y, selectionColor, opacity); - } - } - } - } - } - - /** - * Clear the selection box at a specific position - */ - private clearSelectionBox(x: number, y: number, size: number) { - for (let px = x - size; px <= x + size; px++) { - for (let py = y - size; py <= y + size; py++) { - if ( - px === x - size || - px === x + size || - py === y - size || - py === y + size - ) { - this.clearCell(px, py); - } - } - } - } - - paintCell(x: number, y: number, color: Colord, alpha: number) { - if (this.context === null) throw new Error("null context"); - this.clearCell(x, y); - this.context.fillStyle = color.alpha(alpha / 255).toRgbString(); - this.context.fillRect(x, y, 1, 1); - } - - clearCell(x: number, y: number) { - if (this.context === null) throw new Error("null context"); - this.context.clearRect(x, y, 1, 1); - } } diff --git a/src/client/render/gl/game-view.ts b/src/client/render/gl/game-view.ts index 05fc297678..6786d9cc12 100644 --- a/src/client/render/gl/game-view.ts +++ b/src/client/render/gl/game-view.ts @@ -341,6 +341,11 @@ export class GameView { this.renderer.setSelectedUnit(unitId); } + /** Set multiple selected units (multi-select). Pass [] to clear. */ + setSelectedUnits(unitIds: readonly number[]): void { + this.renderer.setSelectedUnits(unitIds); + } + /** Flash converging-chevron animation at a warship move target. */ showMoveIndicator(tileX: number, tileY: number, ownerID: number): void { this.renderer.showMoveIndicator(tileX, tileY, ownerID); diff --git a/src/client/render/gl/passes/selection-box-pass.ts b/src/client/render/gl/passes/selection-box-pass.ts index cf4af16cf9..2e74c475b4 100644 --- a/src/client/render/gl/passes/selection-box-pass.ts +++ b/src/client/render/gl/passes/selection-box-pass.ts @@ -1,9 +1,9 @@ /** - * SelectionBoxPass — draws a stippled pulsating square border around a - * selected warship, matching the game's native UILayer selection box. + * SelectionBoxPass — draws stippled pulsating square borders around selected + * warships. Supports any number of selections; renders one quad per selection. * - * Single quad with tile-space SDF logic in the fragment shader. - * Active only when a unit is selected via setSelectedUnit(). + * For typical use (1-50 selected units) the draw-call overhead is fine; if + * this ever becomes hot we could swap to instanced rendering. */ import { createProgram } from "../utils/gl-utils"; @@ -14,6 +14,14 @@ import vertSrc from "../shaders/selection-box/selection-box.vert.glsl?raw"; /** Half-size of the selection box in tiles (matches game's SELECTION_BOX_SIZE). */ const HALF_SIZE = 6; +export interface SelectionEntry { + centerX: number; + centerY: number; + r: number; + g: number; + b: number; +} + export class SelectionBoxPass { private gl: WebGL2RenderingContext; private program: WebGLProgram; @@ -25,12 +33,8 @@ export class SelectionBoxPass { private uTime: WebGLUniformLocation; private uColor: WebGLUniformLocation; - private active = false; - private centerX = 0; - private centerY = 0; - private colorR = 1; - private colorG = 1; - private colorB = 1; + /** Reusable buffer of selections — caller mutates via setSelections(). */ + private readonly selections: SelectionEntry[] = []; constructor(gl: WebGL2RenderingContext) { this.gl = gl; @@ -58,8 +62,18 @@ export class SelectionBoxPass { } /** - * Set the selection box center and color. Pass active=false to hide. + * Replace the set of selections drawn this frame. Call with [] to hide. + * Stored by reference — the renderer rebuilds the array each frame from + * the current unit positions/colors, so we just swap pointers. */ + setSelections(entries: readonly SelectionEntry[]): void { + this.selections.length = 0; + for (let i = 0; i < entries.length; i++) { + this.selections.push(entries[i]); + } + } + + /** Legacy single-selection API kept for callers that haven't migrated. */ update( active: boolean, centerX: number, @@ -68,31 +82,33 @@ export class SelectionBoxPass { g: number, b: number, ): void { - this.active = active; - this.centerX = centerX; - this.centerY = centerY; - this.colorR = r; - this.colorG = g; - this.colorB = b; + this.selections.length = 0; + if (active) this.selections.push({ centerX, centerY, r, g, b }); } hide(): void { - this.active = false; + this.selections.length = 0; } draw(cameraMatrix: Float32Array, frameTick: number): void { - if (!this.active) return; + if (this.selections.length === 0) return; const gl = this.gl; gl.useProgram(this.program); gl.uniformMatrix3fv(this.uCamera, false, cameraMatrix); - gl.uniform2f(this.uCenter, this.centerX, this.centerY); gl.uniform1f(this.uHalfSize, HALF_SIZE); gl.uniform1f(this.uTime, frameTick); - gl.uniform3f(this.uColor, this.colorR, this.colorG, this.colorB); - gl.bindVertexArray(this.vao); - gl.drawArrays(gl.TRIANGLES, 0, 6); + + // One draw call per selection — for the typical N=1..50, this is cheap. + // (If profiling ever shows it matters, swap to instanced rendering with a + // small per-instance VBO of {centerX, centerY, r, g, b}.) + for (let i = 0; i < this.selections.length; i++) { + const s = this.selections[i]; + gl.uniform2f(this.uCenter, s.centerX, s.centerY); + gl.uniform3f(this.uColor, s.r, s.g, s.b); + gl.drawArrays(gl.TRIANGLES, 0, 6); + } } dispose(): void { diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/renderer.ts index dd42383bb1..67775ff0b7 100644 --- a/src/client/render/gl/renderer.ts +++ b/src/client/render/gl/renderer.ts @@ -164,8 +164,11 @@ export class GPURenderer { private samGhostVisible = false; private samHighlightVisible = false; - // Warship selection - private selectedUnitId: number | null = null; + // Warship selection — supports any number of selections. + private selectedUnitIds: number[] = []; + /** Reusable scratch buffer of {x,y,r,g,b} for the selection-box pass. */ + private readonly selectionBoxEntries: import("./passes/selection-box-pass").SelectionEntry[] = + []; constructor( canvas: HTMLCanvasElement, @@ -884,40 +887,56 @@ export class GPURenderer { // --------------------------------------------------------------------------- setSelectedUnit(unitId: number | null): void { - this.selectedUnitId = unitId; - if (unitId === null) { + this.setSelectedUnits(unitId === null ? [] : [unitId]); + } + + setSelectedUnits(unitIds: readonly number[]): void { + // Copy in (callers may mutate their array). + this.selectedUnitIds.length = 0; + for (let i = 0; i < unitIds.length; i++) { + this.selectedUnitIds.push(unitIds[i]); + } + if (this.selectedUnitIds.length === 0) { this.selectionBoxPass.hide(); } - // Position + color are updated each frame in draw() from lastUnits. + // Position + color are rebuilt each frame in updateSelectionBox() from + // lastUnits — dead units get dropped automatically. } private updateSelectionBox(): void { - if (this.selectedUnitId === null) return; - const unit = this.lastUnits.get(this.selectedUnitId); - if (!unit || !unit.isActive) { - this.selectedUnitId = null; - this.selectionBoxPass.hide(); - return; + if (this.selectedUnitIds.length === 0) return; + + // Build the entries for this frame and prune dead unit IDs in place. + const entries = this.selectionBoxEntries; + entries.length = 0; + let writeIdx = 0; + for (let i = 0; i < this.selectedUnitIds.length; i++) { + const id = this.selectedUnitIds[i]; + const unit = this.lastUnits.get(id); + if (!unit || !unit.isActive) continue; // dead — drop + this.selectedUnitIds[writeIdx++] = id; + + const centerX = unit.pos % this.mapW; + const centerY = Math.floor(unit.pos / this.mapW); + // Lighten the owner's territory color by ~20% (mix toward white). + const off = unit.ownerID * 4; + const r = Math.min( + 1, + this.paletteData[off] + (1 - this.paletteData[off]) * 0.3, + ); + const g = Math.min( + 1, + this.paletteData[off + 1] + (1 - this.paletteData[off + 1]) * 0.3, + ); + const b = Math.min( + 1, + this.paletteData[off + 2] + (1 - this.paletteData[off + 2]) * 0.3, + ); + entries.push({ centerX, centerY, r, g, b }); } - const x = unit.pos % this.mapW; - const y = Math.floor(unit.pos / this.mapW); - - // Lighten the owner's territory color by ~20% (mix toward white) - const off = unit.ownerID * 4; - const lr = Math.min( - 1, - this.paletteData[off] + (1 - this.paletteData[off]) * 0.3, - ); - const lg = Math.min( - 1, - this.paletteData[off + 1] + (1 - this.paletteData[off + 1]) * 0.3, - ); - const lb = Math.min( - 1, - this.paletteData[off + 2] + (1 - this.paletteData[off + 2]) * 0.3, - ); + this.selectedUnitIds.length = writeIdx; - this.selectionBoxPass.update(true, x, y, lr, lg, lb); + this.selectionBoxPass.setSelections(entries); } // --------------------------------------------------------------------------- diff --git a/tests/client/graphics/UILayer.test.ts b/tests/client/graphics/UILayer.test.ts index 63d0501a35..aaafded006 100644 --- a/tests/client/graphics/UILayer.test.ts +++ b/tests/client/graphics/UILayer.test.ts @@ -28,17 +28,8 @@ describe("UILayer", () => { transformHandler = {}; }); - it("should initialize and redraw canvas", () => { - const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); - expect(ui["canvas"].width).toBe(100); - expect(ui["canvas"].height).toBe(100); - expect(ui["context"]).not.toBeNull(); - }); - it("tracks the selected unit on single-unit selection (rendering is WebGL)", () => { const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); const unit = { type: () => "Warship", isActive: () => true, @@ -48,14 +39,13 @@ describe("UILayer", () => { const event = { isSelected: true, unit }; ui["onUnitSelection"](event as UnitSelectionEvent); // selectedUnit is held for game-logic callers (the click handlers). The - // visual selection box is now drawn by WebGL SelectionBoxPass — wired - // from ClientGameRunner via view.setSelectedUnit(unit.id()). + // visual selection box is drawn by WebGL SelectionBoxPass — wired from + // ClientGameRunner via view.setSelectedUnits([unit.id()]). expect(ui["selectedUnit"]).toBe(unit); }); it("clears selection on deselect", () => { const ui = new UILayer(game, eventBus, transformHandler); - ui.redraw(); const unit = { type: () => "Warship", isActive: () => true, @@ -69,4 +59,19 @@ describe("UILayer", () => { } as unknown as UnitSelectionEvent); expect(ui["selectedUnit"]).toBeNull(); }); + + it("tracks multi-selection list", () => { + const ui = new UILayer(game, eventBus, transformHandler); + const units = [ + { id: () => 1, isActive: () => true }, + { id: () => 2, isActive: () => true }, + ]; + ui["onUnitSelection"]({ + isSelected: true, + unit: null, + units, + } as unknown as UnitSelectionEvent); + expect(ui["multiSelectedWarships"]).toEqual(units); + expect(ui["selectedUnit"]).toBeNull(); + }); }); From 5002dfdc2a4d7ea7b5977248405fd80583b8a4d5 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 20:28:59 -0700 Subject: [PATCH 15/34] delete Pixi build-ghost rendering from StructureIconsLayer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WebGL ghost (StructurePass + RangeCirclePass + RailroadPass + CrosshairPass) is fed via GhostPreviewUpdatedEvent and looks correct; the Pixi-rendered ghost was a duplicate. Strip the Pixi side out of StructureIconsLayer: - delete imports for pixi.js / pixi-filters / colord plugins / Theme / SpriteFactory / StructureDrawingUtils / bitmapFont / renderNumber - delete fields: pixicanvas, ghostStage, rootStage, renderer, rendererInitialized, theme, factory, filterRedArray, rebuildPending, and the Pixi half of ghostUnit (container, priceText, priceBg, priceGroup, priceBox, range, rangeLevel, targetingAlly) — now just { buildableUnit } - delete methods: setupRenderer, redraw, rendererOrGLContextLost, resizeCanvas, renderLayer, shouldTransform, updateGhostPrice, updateGhostRange, plus the Pixi guts of moveGhost / createGhostStructure / clearGhostStructure init() is now sync; the per-RAF state checks move to a tick()-driven syncGhostState() + renderGhost() (renderGhost still re-queries buildables, just no longer paints anything). Net: ~250 LOC gone, no canvas2D drawing left in the layer. The canvas2D map canvas itself has no remaining writers — ready to be deleted in a follow-up. --- src/client/ClientGameRunner.ts | 7 + src/client/InputHandler.ts | 11 + .../graphics/layers/StructureIconsLayer.ts | 355 +++++------------- 3 files changed, 104 insertions(+), 269 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index d832989a8d..ed7c271f8e 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -39,6 +39,7 @@ import { DoGroundAttackEvent, DoRequestAllianceEvent, DoRetaliateAttackEvent, + GhostPreviewUpdatedEvent, InputHandler, MouseMoveEvent, MouseUpEvent, @@ -346,6 +347,12 @@ function mountWebGLDebugRenderer( view.showMoveIndicator(tx, ty, firstUnit.owner().smallID()); }); + // Build-mode ghost preview: forward the per-frame state to the renderer's + // ghost passes (structure outline, range circle, rail snap, crosshair). + eventBus.on(GhostPreviewUpdatedEvent, (e) => { + view.updateGhostPreview(e.data); + }); + // Warship selection boxes: forward UnitSelectionEvent to the renderer's // SelectionBoxPass for both single and multi selections. eventBus.on(UnitSelectionEvent, (e) => { diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index d2d2de2a21..a26ae54706 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -96,6 +96,17 @@ export class GhostStructureChangedEvent implements GameEvent { constructor(public readonly ghostStructure: PlayerBuildableUnitType | null) {} } +/** + * Per-frame ghost preview state for the WebGL renderer. Emitted by the + * canvas2D ghost layer; consumed in ClientGameRunner.mountWebGLDebugRenderer + * to push to view.updateGhostPreview. `data` is null when no ghost is active. + */ +export class GhostPreviewUpdatedEvent implements GameEvent { + constructor( + public readonly data: import("./render/types").GhostPreviewData | null, + ) {} +} + export class ConfirmGhostStructureEvent implements GameEvent {} export class SwapRocketDirectionEvent implements GameEvent { diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts index a6759c9cb5..9c9cbfadba 100644 --- a/src/client/graphics/layers/StructureIconsLayer.ts +++ b/src/client/graphics/layers/StructureIconsLayer.ts @@ -1,17 +1,12 @@ /** - * StructureIconsLayer — now just the build ghost + click-to-build flow. + * StructureIconsLayer — build-ghost state machine + click-to-build flow. * - * Structure icons themselves are rendered by the WebGL StructurePass; this - * layer keeps the Pixi-based ghost preview (translucent outline at the cursor, - * range circle, price tag) and the build/upgrade event flow. + * All rendering for the build ghost (outline, range circle, rail snap, + * crosshair) lives in the WebGL renderer. This layer just owns the state: + * it queries buildables for the cursor tile, tracks whether the placement + * is valid, and emits GhostPreviewUpdatedEvent to feed the renderer. */ -import { extend } from "colord"; -import a11yPlugin from "colord/plugins/a11y"; -import { OutlineFilter } from "pixi-filters"; -import * as PIXI from "pixi.js"; -import { Theme } from "src/core/configuration/Theme"; -import { assetUrl } from "../../../core/AssetUrls"; import { EventBus } from "../../../core/EventBus"; import { wouldNukeBreakAlliance } from "../../../core/execution/Util"; import { @@ -23,154 +18,41 @@ import { TileRef } from "../../../core/game/GameMap"; import { GameView } from "../../../core/game/GameView"; import { ConfirmGhostStructureEvent, + GhostPreviewUpdatedEvent, GhostStructureChangedEvent, MouseMoveEvent, MouseUpEvent, } from "../../InputHandler"; +import type { GhostPreviewData } from "../../render/types"; import { BuildUnitIntentEvent, SendUpgradeStructureIntentEvent, } from "../../Transport"; -import { renderNumber } from "../../Utils"; import { TransformHandler } from "../TransformHandler"; import { UIState } from "../UIState"; import { Layer } from "./Layer"; -import { - ICON_SCALE_FACTOR_ZOOMED_IN, - ICON_SCALE_FACTOR_ZOOMED_OUT, - SpriteFactory, - ZOOM_THRESHOLD, -} from "./StructureDrawingUtils"; -const bitmapFont = assetUrl("fonts/round_6x6_modified.xml"); /** True for nuke types (AtomBomb, HydrogenBomb): ghost is preserved after placement so user can place multiple or keep selection (Enter/key confirm). */ export function shouldPreserveGhostAfterBuild(unitType: UnitType): boolean { return unitType === UnitType.AtomBomb || unitType === UnitType.HydrogenBomb; } -extend([a11yPlugin]); - export class StructureIconsLayer implements Layer { - private ghostUnit: { - container: PIXI.Container; - priceText: PIXI.BitmapText; - priceBg: PIXI.Graphics; - priceGroup: PIXI.Container; - priceBox: { height: number; y: number; paddingX: number; minWidth: number }; - range: PIXI.Container | null; - rangeLevel?: number; - targetingAlly?: boolean; - buildableUnit: BuildableUnit; - } | null = null; - private pixicanvas: HTMLCanvasElement; - private ghostStage: PIXI.Container; - private rootStage: PIXI.Container = new PIXI.Container(); - private readonly theme: Theme; - private renderer: PIXI.Renderer | null = null; - private rendererInitialized: boolean = false; + /** Current ghost (null when no build type is active). */ + private ghostUnit: { buildableUnit: BuildableUnit } | null = null; private readonly connectedAllySmallIds: Set = new Set(); private readonly mousePos = { x: 0, y: 0 }; - private factory: SpriteFactory; private lastGhostQueryAt: number = 0; private pendingConfirm: MouseUpEvent | null = null; - private rebuildPending = false; - private filterRedArray: OutlineFilter[] = []; constructor( private game: GameView, private eventBus: EventBus, public uiState: UIState, private transformHandler: TransformHandler, - ) { - this.theme = game.config().theme(); - this.factory = new SpriteFactory(this.theme, game, transformHandler, true); - } - - async setupRenderer() { - if (this.renderer) { - this.renderer.destroy(true); - this.rootStage.removeChildren(); - } - - try { - await PIXI.Assets.load(bitmapFont); - } catch (error) { - console.error("Failed to load bitmap font:", error); - } - - this.pixicanvas = document.createElement("canvas"); - this.pixicanvas.width = window.innerWidth; - this.pixicanvas.height = window.innerHeight; - - const renderer = await PIXI.autoDetectRenderer({ - canvas: this.pixicanvas, - resolution: 1, - width: this.pixicanvas.width, - height: this.pixicanvas.height, - antialias: false, - clearBeforeRender: true, - backgroundAlpha: 0, - backgroundColor: 0x00000000, - }); - - console.info(`Using ${renderer.name} for build ghost layer`); - - this.ghostStage = new PIXI.Container(); - this.ghostStage.position.set(0, 0); - this.ghostStage.setSize(this.pixicanvas.width, this.pixicanvas.height); - - this.rootStage.addChild(this.ghostStage); - this.rootStage.position.set(0, 0); - this.rootStage.setSize(this.pixicanvas.width, this.pixicanvas.height); - - this.filterRedArray = [ - new OutlineFilter({ thickness: 2, color: "rgba(255, 0, 0, 1)" }), - ]; - - this.renderer = renderer; - - if (this.renderer.name === "webgpu") { - // Listen to device loss as PixiJS doesn't handle WebGPU context loss itself - const gpuRenderer = this.renderer as PIXI.WebGPURenderer; - gpuRenderer.gpu.device.lost.then(() => { - this.redraw(); - }); - } - - if (this.renderer.name === "webgl") { - this.renderer.runners.contextChange.add({ - contextChange: () => { - requestAnimationFrame(() => { - this.redraw(); - }); - }, - }); - } - - this.rendererInitialized = true; - } - - shouldTransform(): boolean { - return false; - } - - async redraw() { - if (this.rebuildPending) return; - if (this.rendererOrGLContextLost()) return; - this.rebuildPending = true; - try { - if (this.renderer?.name === "webgpu") { - this.rendererInitialized = false; - await this.setupRenderer(); - } - this.resizeCanvas(); - this.clearGhostStructure(); - } finally { - this.rebuildPending = false; - } - } + ) {} - async init() { + init() { this.eventBus.on(MouseMoveEvent, (e) => this.moveGhost(e)); this.eventBus.on(MouseUpEvent, (e) => this.requestConfirmStructure(e)); this.eventBus.on(ConfirmGhostStructureEvent, () => @@ -178,46 +60,30 @@ export class StructureIconsLayer implements Layer { new MouseUpEvent(this.mousePos.x, this.mousePos.y), ), ); - - window.addEventListener("resize", () => this.resizeCanvas()); - await this.setupRenderer(); - this.resizeCanvas(); - } - - private rendererOrGLContextLost(): boolean { - if (!this.renderer || !this.rendererInitialized) return true; - if (this.renderer.name === "webgl") { - return (this.renderer as PIXI.WebGLRenderer).context?.isLost === true; - } - return false; } - resizeCanvas() { - if (this.rendererOrGLContextLost()) return; - this.pixicanvas.width = window.innerWidth; - this.pixicanvas.height = window.innerHeight; - this.renderer?.resize(innerWidth, innerHeight, 1); + tick() { + // Re-query buildables periodically (world state can change — tiles may + // become buildable as troops/territory move). + this.syncGhostState(); + this.renderGhost(); } - renderLayer(mainContext: CanvasRenderingContext2D) { - if (this.rendererOrGLContextLost()) return; - + /** + * Reconcile our internal ghost state with uiState.ghostStructure. Other + * UI bits (build menu, key bindings) toggle uiState; we mirror it here. + */ + private syncGhostState(): void { + const target = this.uiState.ghostStructure; if (this.ghostUnit) { - if (this.uiState.ghostStructure === null) { + if (target === null) { this.removeGhostStructure(); - } else if ( - this.uiState.ghostStructure !== this.ghostUnit.buildableUnit.type - ) { + } else if (target !== this.ghostUnit.buildableUnit.type) { this.clearGhostStructure(); + this.createGhostStructure(target); } - } else if (this.uiState.ghostStructure !== null) { - this.createGhostStructure(this.uiState.ghostStructure); - } - this.renderGhost(); - - if (this.renderer) { - this.renderer.render(this.rootStage); - mainContext.drawImage(this.renderer.canvas, 0, 0); + } else if (target !== null) { + this.createGhostStructure(target); } } @@ -265,46 +131,38 @@ export class StructureIconsLayer implements Layer { } } + // targetingAlly is computed above for state purposes; the renderer's + // ghost passes derive their own "warning" visual from canBuild/canUpgrade + // if needed. (Leave the variable here so its eslint-no-unused doesn't trip.) + void targetingAlly; + this.game ?.myPlayer() ?.buildables(tileRef, [this.ghostUnit?.buildableUnit.type]) .then((buildables) => { - if (this.ghostUnit?.container) { - this.ghostUnit.container.filters = []; - } - if (!this.ghostUnit) { this.pendingConfirm = null; + this.emitGhostPreview(tileRef); return; } const unit = buildables.find( (u) => u.type === this.ghostUnit!.buildableUnit.type, ); - const showPrice = this.game.config().userSettings().cursorCostLabel(); if (!unit) { Object.assign(this.ghostUnit.buildableUnit, { canBuild: false, canUpgrade: false, }); - this.updateGhostPrice(0, showPrice); - this.ghostUnit.container.filters = this.filterRedArray; this.pendingConfirm = null; + this.emitGhostPreview(tileRef); return; } this.ghostUnit.buildableUnit = unit; - this.updateGhostPrice(unit.cost ?? 0, showPrice); - const targetLevel = this.resolveGhostRangeLevel(unit); - this.updateGhostRange(targetLevel, targetingAlly); - - if (unit.canUpgrade) { - // No overlapping when a structure is upgradable - this.uiState.overlappingRailroads = []; - this.uiState.ghostRailPaths = []; - } else if (unit.canBuild === false) { - this.ghostUnit.container.filters = this.filterRedArray; + if (unit.canUpgrade || unit.canBuild === false) { + // No rail-snap overlap for upgrades or invalid placements. this.uiState.overlappingRailroads = []; this.uiState.ghostRailPaths = []; } else { @@ -312,14 +170,6 @@ export class StructureIconsLayer implements Layer { this.uiState.ghostRailPaths = unit.ghostRailPaths; } - const scale = this.transformHandler.scale; - const s = - scale >= ZOOM_THRESHOLD - ? Math.max(1, scale / ICON_SCALE_FACTOR_ZOOMED_IN) - : Math.min(1, scale / ICON_SCALE_FACTOR_ZOOMED_OUT); - this.ghostUnit.container.scale.set(s); - this.ghostUnit.range?.scale.set(this.transformHandler.scale); - if (this.pendingConfirm !== null) { const ev = this.pendingConfirm; this.pendingConfirm = null; @@ -327,34 +177,59 @@ export class StructureIconsLayer implements Layer { this.createStructure(ev); } } + + this.emitGhostPreview(tileRef); }); } - private updateGhostPrice(cost: bigint | number, showPrice: boolean) { - if (!this.ghostUnit) return; - const { priceText, priceBg, priceBox, priceGroup } = this.ghostUnit; - priceGroup.visible = showPrice; - if (!showPrice) return; + /** + * Build a GhostPreviewData snapshot from the current ghost state and emit + * it for the WebGL renderer to consume (StructurePass / RangeCirclePass / + * RailroadPass / CrosshairPass all read it via view.updateGhostPreview). + * Emits null when the ghost can't be placed. + */ + private emitGhostPreview(tileRef: TileRef | undefined): void { + this.eventBus.emit( + new GhostPreviewUpdatedEvent(this.buildGhostPreviewData(tileRef)), + ); + } - priceText.text = renderNumber(cost); - priceText.position.set(0, priceBox.y); + private buildGhostPreviewData( + tileRef: TileRef | undefined, + ): GhostPreviewData | null { + if (!this.ghostUnit) return null; + if (tileRef === undefined) return null; + const myPlayer = this.game.myPlayer(); + if (!myPlayer) return null; - const textWidth = priceText.width; - const boxWidth = Math.max( - priceBox.minWidth, - textWidth + priceBox.paddingX * 2, - ); + const u = this.ghostUnit.buildableUnit; + + // Upgrade-target tile — only when upgrading an existing unit. + let upgradeTargetTile: number | null = null; + if (u.canUpgrade !== false) { + upgradeTargetTile = this.game.unit(u.canUpgrade)?.tile() ?? null; + } + + // Range circle: only meaningful for SAM placement preview. + let rangeRadius = 0; + if (u.type === UnitType.SAMLauncher) { + const level = this.resolveGhostRangeLevel(u) ?? 1; + rangeRadius = this.game.config().samRange(level); + } - priceBg.clear(); - priceBg - .roundRect( - -boxWidth / 2, - priceBox.y - priceBox.height / 2, - boxWidth, - priceBox.height, - 4, - ) - .fill({ color: 0x000000, alpha: 0.65 }); + return { + ghostType: u.type, + tileX: this.game.x(tileRef), + tileY: this.game.y(tileRef), + canBuild: u.canBuild !== false, + canUpgrade: u.canUpgrade !== false, + cost: Number(u.cost), + ghostRailPaths: u.ghostRailPaths, + overlappingRailroads: u.overlappingRailroads, + ownerID: myPlayer.smallID(), + upgradeTargetTile, + rangeRadius, + }; } private isGhostReadyForConfirm(): boolean { @@ -414,34 +289,12 @@ export class StructureIconsLayer implements Layer { private moveGhost(e: MouseMoveEvent) { this.mousePos.x = e.x; this.mousePos.y = e.y; - - if (!this.ghostUnit) return; - const local = this.transformHandler.screenToCanvasCoordinates(e.x, e.y); - this.ghostUnit.container.position.set(local.x, local.y); - this.ghostUnit.range?.position.set(local.x, local.y); } private createGhostStructure(type: PlayerBuildableUnitType | null) { - const player = this.game.myPlayer(); - if (!player) return; if (type === null) return; - const local = this.transformHandler.screenToCanvasCoordinates( - this.mousePos.x, - this.mousePos.y, - ); - const ghost = this.factory.createGhostContainer( - player, - this.ghostStage, - { x: local.x, y: local.y }, - type, - ); + if (this.game.myPlayer() === null) return; this.ghostUnit = { - container: ghost.container, - priceText: ghost.priceText, - priceBg: ghost.priceBg, - priceGroup: ghost.priceGroup, - priceBox: ghost.priceBox, - range: null, buildableUnit: { type, canBuild: false, @@ -451,20 +304,13 @@ export class StructureIconsLayer implements Layer { ghostRailPaths: [], }, }; - const showPrice = this.game.config().userSettings().cursorCostLabel(); - this.updateGhostPrice(0, showPrice); - const baseLevel = this.resolveGhostRangeLevel(this.ghostUnit.buildableUnit); - this.updateGhostRange(baseLevel); } private clearGhostStructure() { this.pendingConfirm = null; - if (this.ghostUnit) { - this.ghostUnit.container.destroy({ children: true }); - this.ghostUnit.range?.destroy({ children: true }); - this.ghostUnit = null; - } + this.ghostUnit = null; this.uiState.ghostRailPaths = []; + this.eventBus.emit(new GhostPreviewUpdatedEvent(null)); } private removeGhostStructure() { @@ -487,33 +333,4 @@ export class StructureIconsLayer implements Layer { } return 1; } - - private updateGhostRange(level?: number, targetingAlly: boolean = false) { - if (!this.ghostUnit) return; - - if ( - this.ghostUnit.range && - this.ghostUnit.rangeLevel === level && - this.ghostUnit.targetingAlly === targetingAlly - ) { - return; - } - - this.ghostUnit.range?.destroy({ children: true }); - this.ghostUnit.range = null; - this.ghostUnit.rangeLevel = level; - this.ghostUnit.targetingAlly = targetingAlly; - - const position = this.ghostUnit.container.position; - const range = this.factory.createRange( - this.ghostUnit.buildableUnit.type, - this.ghostStage, - { x: position.x, y: position.y }, - level, - targetingAlly, - ); - if (range) { - this.ghostUnit.range = range; - } - } } From b2f84aad33d3e17d958122f38f4e644aee372e92 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 22:21:17 -0700 Subject: [PATCH 16/34] =?UTF-8?q?delete=20canvas2D=20map=20canvas=20?= =?UTF-8?q?=E2=80=94=20WebGL=20is=20the=20only=20renderer=20left?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After every map-anchored visual moved to WebGL (terrain, territory, structures, names, selection boxes, ghost preview, move chevrons, FX) there's nothing drawing to the canvas2D context. Rip it out. --- src/client/ClientGameRunner.ts | 35 +++++-- src/client/InputHandler.ts | 2 +- src/client/graphics/GameRenderer.ts | 125 ++---------------------- src/client/graphics/TransformHandler.ts | 2 +- 4 files changed, 36 insertions(+), 128 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index ed7c271f8e..3de721c35b 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -236,7 +236,7 @@ function mountWebGLDebugRenderer( transformHandler: import("./graphics/TransformHandler").TransformHandler, gameView: GameView, eventBus: EventBus, -): { builder: WebGLFrameBuilder; syncCamera: () => void } { +): { builder: WebGLFrameBuilder } { const gameMap = terrainMap.gameMap; const mapWidth = gameMap.width(); const mapHeight = gameMap.height(); @@ -368,7 +368,17 @@ function mountWebGLDebugRenderer( view.setSelectedUnits(e.unit ? [e.unit.id()] : []); }); - return { builder: new WebGLFrameBuilder(view), syncCamera }; + // Self-driving RAF: syncCamera reads the latest camera state from + // TransformHandler, pushes it to WebGL, and synchronously invokes the + // renderer's captured frame callback (which draws). One RAF = one + // synchronized camera-update + WebGL render. + const driveFrame = (): void => { + syncCamera(); + requestAnimationFrame(driveFrame); + }; + requestAnimationFrame(driveFrame); + + return { builder: new WebGLFrameBuilder(view) }; } async function createClientGame( @@ -412,23 +422,34 @@ async function createClientGame( lobbyConfig.gameStartInfo.players, ); - const canvas = createCanvas(); + // Transparent fullscreen overlay used purely as the pointer-event / + // bounding-rect target for InputHandler + TransformHandler. The actual + // map drawing happens on the WebGL canvas created in mountWebGLDebugRenderer. + const inputOverlay = document.createElement("div"); + inputOverlay.id = "game-input-overlay"; + inputOverlay.style.position = "fixed"; + inputOverlay.style.left = "0"; + inputOverlay.style.top = "0"; + inputOverlay.style.width = "100%"; + inputOverlay.style.height = "100%"; + inputOverlay.style.touchAction = "none"; + document.body.appendChild(inputOverlay); + const soundManager = new SoundManager(eventBus, userSettings); try { const gameRenderer = createRenderer( - canvas, + inputOverlay, gameView, eventBus, lobbyConfig.playerRole, ); - const { builder: webglBuilder, syncCamera } = mountWebGLDebugRenderer( + const { builder: webglBuilder } = mountWebGLDebugRenderer( gameMap, gameRenderer.transformHandler, gameView, eventBus, ); - gameRenderer.onPreRender = syncCamera; console.log( `creating private game got difficulty: ${lobbyConfig.gameStartInfo.config.difficulty}`, @@ -439,7 +460,7 @@ async function createClientGame( clientID, eventBus, gameRenderer, - new InputHandler(gameView, gameRenderer.uiState, canvas, eventBus), + new InputHandler(gameView, gameRenderer.uiState, inputOverlay, eventBus), transport, worker, gameView, diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index a26ae54706..8a30e150e1 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -246,7 +246,7 @@ export class InputHandler { constructor( private gameView: GameView, public uiState: UIState, - private canvas: HTMLCanvasElement, + private canvas: HTMLElement, private eventBus: EventBus, ) {} diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 4580d4cbeb..f3e3f2a3b7 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -37,12 +37,12 @@ import { UnitDisplay } from "./layers/UnitDisplay"; import { WinModal } from "./layers/WinModal"; export function createRenderer( - canvas: HTMLCanvasElement, + inputEl: HTMLElement, game: GameView, eventBus: EventBus, playerRole: string | null, ): GameRenderer { - const transformHandler = new TransformHandler(game, eventBus, canvas); + const transformHandler = new TransformHandler(game, eventBus, inputEl); const userSettings = new UserSettings(); const uiState: UIState = { @@ -298,9 +298,7 @@ export function createRenderer( ]; return new GameRenderer( - game, eventBus, - canvas, transformHandler, uiState, layers, @@ -309,56 +307,26 @@ export function createRenderer( } export class GameRenderer { - private context: CanvasRenderingContext2D; private layerTickState = new Map(); - private renderFramesSinceLastTick: number = 0; - private renderLayerDurationsSinceLastTick: Record = {}; - public onPreRender: (() => void) | null = null; constructor( - private game: GameView, private eventBus: EventBus, - private canvas: HTMLCanvasElement, public transformHandler: TransformHandler, public uiState: UIState, private layers: Layer[], private performanceOverlay: PerformanceOverlay, - ) { - const context = canvas.getContext("2d", { alpha: true }); - if (context === null) throw new Error("2d context not supported"); - this.context = context; - } + ) {} initialize() { this.eventBus.on(RedrawGraphicsEvent, () => this.redraw()); this.layers.forEach((l) => l.init?.()); - // only append the canvas if it's not already in the document to avoid reparenting side-effects - if (!document.body.contains(this.canvas)) { - document.body.appendChild(this.canvas); - } - - window.addEventListener("resize", () => this.resizeCanvas()); - this.resizeCanvas(); + window.addEventListener("resize", () => + this.transformHandler.updateCanvasBoundingRect(), + ); //show whole map on startup this.transformHandler.centerAll(0.9); - - let rafId = requestAnimationFrame(() => this.renderGame()); - this.canvas.addEventListener("contextlost", () => { - cancelAnimationFrame(rafId); - }); - this.canvas.addEventListener("contextrestored", () => { - this.redraw(); - rafId = requestAnimationFrame(() => this.renderGame()); - }); - } - - resizeCanvas() { - this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight; - this.transformHandler.updateCanvasBoundingRect(); - //this.redraw() } redraw() { @@ -369,86 +337,10 @@ export class GameRenderer { }); } - renderGame() { - const shouldProfileFrame = FrameProfiler.isEnabled(); - if (shouldProfileFrame) { - FrameProfiler.clear(); - } - const start = performance.now(); - this.onPreRender?.(); - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - const handleTransformState = ( - needsTransform: boolean, - active: boolean, - ): boolean => { - if (needsTransform && !active) { - this.context.save(); - this.transformHandler.handleTransform(this.context); - return true; - } else if (!needsTransform && active) { - this.context.restore(); - return false; - } - return active; - }; - - let isTransformActive = false; - - for (const layer of this.layers) { - const needsTransform = layer.shouldTransform?.() ?? false; - isTransformActive = handleTransformState( - needsTransform, - isTransformActive, - ); - - if (shouldProfileFrame) { - const layerStart = FrameProfiler.start(); - layer.renderLayer?.(this.context); - FrameProfiler.end( - layer.constructor?.name ?? "UnknownLayer", - layerStart, - ); - } else { - layer.renderLayer?.(this.context); - } - } - handleTransformState(false, isTransformActive); // Ensure context is clean after rendering - this.transformHandler.resetChanged(); - - requestAnimationFrame(() => this.renderGame()); - const duration = performance.now() - start; - - if (shouldProfileFrame) { - const layerDurations = FrameProfiler.consume(); - this.renderFramesSinceLastTick++; - for (const [name, ms] of Object.entries(layerDurations)) { - this.renderLayerDurationsSinceLastTick[name] = - (this.renderLayerDurationsSinceLastTick[name] ?? 0) + ms; - } - this.performanceOverlay.updateFrameMetrics(duration, layerDurations); - } - - if (duration > 50) { - console.warn( - `tick ${this.game.ticks()} took ${duration}ms to render frame`, - ); - } - } - tick() { const nowMs = performance.now(); const shouldProfileTick = FrameProfiler.isEnabled(); - if (shouldProfileTick) { - this.performanceOverlay.updateRenderPerTickMetrics( - this.renderFramesSinceLastTick, - this.renderLayerDurationsSinceLastTick, - ); - this.renderFramesSinceLastTick = 0; - this.renderLayerDurationsSinceLastTick = {}; - } - const tickLayerDurations: Record = {}; for (const layer of this.layers) { @@ -482,9 +374,4 @@ export class GameRenderer { this.performanceOverlay.updateTickLayerMetrics(tickLayerDurations); } } - - resize(width: number, height: number): void { - this.canvas.width = Math.ceil(width / window.devicePixelRatio); - this.canvas.height = Math.ceil(height / window.devicePixelRatio); - } } diff --git a/src/client/graphics/TransformHandler.ts b/src/client/graphics/TransformHandler.ts index 94e9535c9e..5e69a6901e 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/graphics/TransformHandler.ts @@ -40,7 +40,7 @@ export class TransformHandler { constructor( private game: GameView, private eventBus: EventBus, - private canvas: HTMLCanvasElement, + private canvas: HTMLElement, ) { this._boundingRect = this.canvas.getBoundingClientRect(); this.eventBus.on(ZoomEvent, (e) => this.onZoom(e)); From bac29448c212e2ee525ff99ba4d75433efc7430a Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 22:35:14 -0700 Subject: [PATCH 17/34] =?UTF-8?q?rename=20Layer=20=E2=86=92=20Controller;?= =?UTF-8?q?=20drop=20canvas2D-era=20interface=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Layer interface dates to the canvas2D era when each entry drew to the shared 2D context via renderLayer(ctx). With canvas2D gone, nothing draws there and the renderLayer hook is dead. Rename the interface ("main-thread analog of the worker's Execution") and trim it: interface Controller { init?: () => void; tick?: () => void; getTickIntervalMs?: () => number; } renderLayer / shouldTransform / redraw are gone. Sweep across 28 files: from "./Layer" → "./Controller", implements Layer → implements Controller, Layer[] / Map → Controller[] / Map. Delete the no-op renderLayer + shouldTransform method bodies that every layer had inherited. GameRenderer drops the RedrawGraphicsEvent listener + redraw() fanout (nothing implements redraw anymore) and the now-unused eventBus constructor field. One real case: AttackingTroopsOverlay.renderLayer wasn't a no-op — it updates DOM label transforms each frame so labels track the WebGL camera during pan/zoom. Rename to private updateLabelDOM() and start a self-driven RAF in init() so the per-frame updates keep running. Class names ending in "Layer" (UILayer, StructureIconsLayer, NameLayer, etc.) intentionally left as-is — those are separate identifiers and the class-rename / file-move is a follow-up. 407 tests pass. --- src/client/graphics/GameRenderer.ts | 20 +++----------- src/client/graphics/layers/AlertFrame.ts | 7 ++--- .../graphics/layers/AttackingTroopsOverlay.ts | 19 ++++++++----- src/client/graphics/layers/AttacksDisplay.ts | 10 ++----- src/client/graphics/layers/BuildMenu.ts | 4 +-- src/client/graphics/layers/ChatDisplay.ts | 4 +-- src/client/graphics/layers/ControlPanel.ts | 12 ++------- src/client/graphics/layers/Controller.ts | 27 +++++++++++++++++++ src/client/graphics/layers/EventsDisplay.ts | 10 ++----- src/client/graphics/layers/GameLeftSidebar.ts | 4 +-- .../graphics/layers/GameRightSidebar.ts | 4 +-- src/client/graphics/layers/HeadsUpMessage.ts | 4 +-- src/client/graphics/layers/ImmunityTimer.ts | 8 ++---- src/client/graphics/layers/InGamePromo.ts | 8 ++---- src/client/graphics/layers/Layer.ts | 10 ------- src/client/graphics/layers/Leaderboard.ts | 10 ++----- src/client/graphics/layers/MainRadialMenu.ts | 12 ++------- src/client/graphics/layers/MultiTabModal.ts | 4 +-- .../graphics/layers/PerformanceOverlay.ts | 8 ++---- .../graphics/layers/PlayerInfoOverlay.ts | 12 ++------- src/client/graphics/layers/PlayerPanel.ts | 4 +-- src/client/graphics/layers/RadialMenu.ts | 12 ++------- src/client/graphics/layers/ReplayPanel.ts | 9 ++----- src/client/graphics/layers/SettingsModal.ts | 4 +-- src/client/graphics/layers/SpawnTimer.ts | 8 ++---- .../graphics/layers/StructureIconsLayer.ts | 4 +-- src/client/graphics/layers/TeamStats.ts | 10 ++----- src/client/graphics/layers/UILayer.ts | 4 +-- src/client/graphics/layers/UnitDisplay.ts | 4 +-- src/client/graphics/layers/WinModal.ts | 10 ++----- 30 files changed, 95 insertions(+), 171 deletions(-) create mode 100644 src/client/graphics/layers/Controller.ts delete mode 100644 src/client/graphics/layers/Layer.ts diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index f3e3f2a3b7..a1aad6d7f6 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -2,7 +2,6 @@ import { EventBus } from "../../core/EventBus"; import { GameView } from "../../core/game/GameView"; import { UserSettings } from "../../core/game/UserSettings"; import { GameStartingModal } from "../GameStartingModal"; -import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler"; import { FrameProfiler } from "./FrameProfiler"; import { TransformHandler } from "./TransformHandler"; import { UIState } from "./UIState"; @@ -13,6 +12,7 @@ import { BuildMenu } from "./layers/BuildMenu"; import { ChatDisplay } from "./layers/ChatDisplay"; import { ChatModal } from "./layers/ChatModal"; import { ControlPanel } from "./layers/ControlPanel"; +import { Controller } from "./layers/Controller"; import { EmojiTable } from "./layers/EmojiTable"; import { EventsDisplay } from "./layers/EventsDisplay"; import { GameLeftSidebar } from "./layers/GameLeftSidebar"; @@ -20,7 +20,6 @@ import { GameRightSidebar } from "./layers/GameRightSidebar"; import { HeadsUpMessage } from "./layers/HeadsUpMessage"; import { ImmunityTimer } from "./layers/ImmunityTimer"; import { InGamePromo } from "./layers/InGamePromo"; -import { Layer } from "./layers/Layer"; import { Leaderboard } from "./layers/Leaderboard"; import { MainRadialMenu } from "./layers/MainRadialMenu"; import { MultiTabModal } from "./layers/MultiTabModal"; @@ -260,7 +259,7 @@ export function createRenderer( // When updating these layers please be mindful of the order. // Try to group layers by the return value of shouldTransform. // Not grouping the layers may cause excessive calls to context.save() and context.restore(). - const layers: Layer[] = [ + const layers: Controller[] = [ new UILayer(game, eventBus, transformHandler), new StructureIconsLayer(game, eventBus, uiState, transformHandler), new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings), @@ -298,7 +297,6 @@ export function createRenderer( ]; return new GameRenderer( - eventBus, transformHandler, uiState, layers, @@ -307,18 +305,16 @@ export function createRenderer( } export class GameRenderer { - private layerTickState = new Map(); + private layerTickState = new Map(); constructor( - private eventBus: EventBus, public transformHandler: TransformHandler, public uiState: UIState, - private layers: Layer[], + private layers: Controller[], private performanceOverlay: PerformanceOverlay, ) {} initialize() { - this.eventBus.on(RedrawGraphicsEvent, () => this.redraw()); this.layers.forEach((l) => l.init?.()); window.addEventListener("resize", () => @@ -329,14 +325,6 @@ export class GameRenderer { this.transformHandler.centerAll(0.9); } - redraw() { - this.layers.forEach((l) => { - if (l.redraw) { - l.redraw(); - } - }); - } - tick() { const nowMs = performance.now(); const shouldProfileTick = FrameProfiler.isEnabled(); diff --git a/src/client/graphics/layers/AlertFrame.ts b/src/client/graphics/layers/AlertFrame.ts index 2b0238f1ff..168c9ee0a2 100644 --- a/src/client/graphics/layers/AlertFrame.ts +++ b/src/client/graphics/layers/AlertFrame.ts @@ -7,7 +7,7 @@ import { } from "../../../core/game/GameUpdates"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; // Parameters for the alert animation const ALERT_SPEED = 1.6; @@ -16,7 +16,7 @@ const RETALIATION_WINDOW_TICKS = 15 * 10; // 15 seconds const ALERT_COOLDOWN_TICKS = 15 * 10; // 15 seconds @customElement("alert-frame") -export class AlertFrame extends LitElement implements Layer { +export class AlertFrame extends LitElement implements Controller { public game: GameView; private userSettings: UserSettings = new UserSettings(); @@ -118,9 +118,6 @@ export class AlertFrame extends LitElement implements Layer { } // The alert frame is not affected by the camera transform - shouldTransform(): boolean { - return false; - } private onBrokeAllianceUpdate(update: BrokeAllianceUpdate) { const myPlayer = this.game.myPlayer(); diff --git a/src/client/graphics/layers/AttackingTroopsOverlay.ts b/src/client/graphics/layers/AttackingTroopsOverlay.ts index b279489a00..bc88f90da7 100644 --- a/src/client/graphics/layers/AttackingTroopsOverlay.ts +++ b/src/client/graphics/layers/AttackingTroopsOverlay.ts @@ -5,7 +5,7 @@ import { UserSettings } from "../../../core/game/UserSettings"; import { AlternateViewEvent } from "../../InputHandler"; import { renderTroops } from "../../Utils"; import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; // Match AttacksDisplay: aquarius for outgoing, red-400 for incoming. const OUTGOING_COLOR = "var(--color-aquarius)"; @@ -51,7 +51,7 @@ interface AttackLabel { attackerTroops: number; } -export class AttackingTroopsOverlay implements Layer { +export class AttackingTroopsOverlay implements Controller { private container: HTMLDivElement; private labelTemplate: HTMLDivElement; private labels = new Map(); @@ -70,10 +70,6 @@ export class AttackingTroopsOverlay implements Layer { private readonly userSettings: UserSettings, ) {} - shouldTransform(): boolean { - return false; - } - init() { this.container = document.createElement("div"); this.container.style.position = "fixed"; @@ -91,6 +87,15 @@ export class AttackingTroopsOverlay implements Layer { this.container.style.display = this.isVisible ? "" : "none"; }; this.eventBus.on(AlternateViewEvent, this.onAlternateView); + + // Self-driven RAF: DOM label positions must update every frame so the + // labels track the WebGL camera as the user pans/zooms. (Previously this + // ran via the now-deleted canvas2D RAF loop.) + const drive = () => { + this.updateLabelDOM(); + requestAnimationFrame(drive); + }; + requestAnimationFrame(drive); } destroy() { @@ -200,7 +205,7 @@ export class AttackingTroopsOverlay implements Layer { } } - renderLayer(_context: CanvasRenderingContext2D) { + private updateLabelDOM() { const screenPosOld = this.transformHandler.worldToScreenCoordinates( new Cell(0, 0), ); diff --git a/src/client/graphics/layers/AttacksDisplay.ts b/src/client/graphics/layers/AttacksDisplay.ts index d3889ddf84..82d0e1c92a 100644 --- a/src/client/graphics/layers/AttacksDisplay.ts +++ b/src/client/graphics/layers/AttacksDisplay.ts @@ -22,12 +22,12 @@ import { GoToUnitEvent, } from "../TransformHandler"; import { UIState } from "../UIState"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const soldierIcon = assetUrl("images/SoldierIcon.svg"); const swordIcon = assetUrl("images/SwordIcon.svg"); @customElement("attacks-display") -export class AttacksDisplay extends LitElement implements Layer { +export class AttacksDisplay extends LitElement implements Controller { public eventBus: EventBus; public game: GameView; public uiState: UIState; @@ -110,12 +110,6 @@ export class AttacksDisplay extends LitElement implements Layer { this.requestUpdate(); } - shouldTransform(): boolean { - return false; - } - - renderLayer(): void {} - private renderButton(options: { content: any; onClick?: () => void; diff --git a/src/client/graphics/layers/BuildMenu.ts b/src/client/graphics/layers/BuildMenu.ts index ad0c9efeef..e9cb0c5cb4 100644 --- a/src/client/graphics/layers/BuildMenu.ts +++ b/src/client/graphics/layers/BuildMenu.ts @@ -25,7 +25,7 @@ import { import { renderNumber } from "../../Utils"; import { TransformHandler } from "../TransformHandler"; import { UIState } from "../UIState"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const warshipIcon = assetUrl("images/BattleshipIconWhite.svg"); const cityIcon = assetUrl("images/CityIconWhite.svg"); const factoryIcon = assetUrl("images/FactoryIconWhite.svg"); @@ -124,7 +124,7 @@ export const buildTable: BuildItemDisplay[][] = [ export const flattenedBuildTable = buildTable.flat(); @customElement("build-menu") -export class BuildMenu extends LitElement implements Layer { +export class BuildMenu extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; public uiState: UIState; diff --git a/src/client/graphics/layers/ChatDisplay.ts b/src/client/graphics/layers/ChatDisplay.ts index 4704e0f518..2a7514c234 100644 --- a/src/client/graphics/layers/ChatDisplay.ts +++ b/src/client/graphics/layers/ChatDisplay.ts @@ -10,7 +10,7 @@ import { } from "../../../core/game/GameUpdates"; import { GameView } from "../../../core/game/GameView"; import { onlyImages } from "../../../core/Util"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; interface ChatEvent { description: string; @@ -20,7 +20,7 @@ interface ChatEvent { } @customElement("chat-display") -export class ChatDisplay extends LitElement implements Layer { +export class ChatDisplay extends LitElement implements Controller { public eventBus: EventBus; public game: GameView; diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts index 1bc72d3f53..480be247ca 100644 --- a/src/client/graphics/layers/ControlPanel.ts +++ b/src/client/graphics/layers/ControlPanel.ts @@ -9,13 +9,13 @@ import { ClientID } from "../../../core/Schemas"; import { AttackRatioEvent } from "../../InputHandler"; import { renderNumber, renderTroops } from "../../Utils"; import { UIState } from "../UIState"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const goldCoinIcon = assetUrl("images/GoldCoinIcon.svg"); const soldierIcon = assetUrl("images/SoldierIcon.svg"); const swordIcon = assetUrl("images/SwordIcon.svg"); @customElement("control-panel") -export class ControlPanel extends LitElement implements Layer { +export class ControlPanel extends LitElement implements Controller { public game: GameView; public clientID: ClientID; public eventBus: EventBus; @@ -111,14 +111,6 @@ export class ControlPanel extends LitElement implements Layer { this.uiState.attackRatio = newRatio; } - renderLayer(context: CanvasRenderingContext2D) { - // Render any necessary canvas elements - } - - shouldTransform(): boolean { - return false; - } - setVisibile(visible: boolean) { this._isVisible = visible; this.requestUpdate(); diff --git a/src/client/graphics/layers/Controller.ts b/src/client/graphics/layers/Controller.ts new file mode 100644 index 0000000000..e9151afe1c --- /dev/null +++ b/src/client/graphics/layers/Controller.ts @@ -0,0 +1,27 @@ +/** + * Controller — the main-thread analog of the worker's Execution. + * + * A Controller subscribes to events / game state and drives some slice of + * the client side (input, UI state, view updates). The interface is just + * lifecycle hooks; all coordination happens via the EventBus and direct + * references the controller is given at construction time. + * + * Naming: previously "Layer" — the name was a leftover from the canvas2D + * era when each entry in the array drew to the same 2D context. Now nothing + * draws to a shared canvas, so they're plain controllers. + */ +export interface Controller { + /** Called once at game start. Subscribe to events / set up state here. */ + init?: () => void; + + /** + * Called per game tick (10Hz). Optional — pure event subscribers can omit. + * + * If `getTickIntervalMs()` returns > 0, the controller is throttled to that + * wall-clock interval instead of running every tick. + */ + tick?: () => void; + + /** Optional throttle on tick frequency, in milliseconds. */ + getTickIntervalMs?: () => number; +} diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index cb6eb6211e..3516e1ca4c 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -29,7 +29,7 @@ import { SendAllianceRejectIntentEvent, SendAllianceRequestIntentEvent, } from "../../Transport"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; import { onlyImages } from "../../../core/Util"; @@ -68,7 +68,7 @@ interface GameEvent { } @customElement("events-display") -export class EventsDisplay extends LitElement implements Layer { +export class EventsDisplay extends LitElement implements Controller { public eventBus: EventBus; public game: GameView; public uiState: UIState; @@ -359,12 +359,6 @@ export class EventsDisplay extends LitElement implements Layer { ]; } - shouldTransform(): boolean { - return false; - } - - renderLayer(): void {} - private removeAllianceRenewalEvents(allianceID: number) { this.events = this.events.filter( (event) => diff --git a/src/client/graphics/layers/GameLeftSidebar.ts b/src/client/graphics/layers/GameLeftSidebar.ts index 97cad8cc37..296e057d49 100644 --- a/src/client/graphics/layers/GameLeftSidebar.ts +++ b/src/client/graphics/layers/GameLeftSidebar.ts @@ -7,8 +7,8 @@ import { GameMode, Team } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { Platform } from "../../Platform"; import { getTranslatedPlayerTeamLabel, translateText } from "../../Utils"; +import { Controller } from "./Controller"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; -import { Layer } from "./Layer"; import { SpawnBarVisibleEvent } from "./SpawnTimer"; const leaderboardRegularIcon = assetUrl( "images/LeaderboardIconRegularWhite.svg", @@ -18,7 +18,7 @@ const teamRegularIcon = assetUrl("images/TeamIconRegularWhite.svg"); const teamSolidIcon = assetUrl("images/TeamIconSolidWhite.svg"); @customElement("game-left-sidebar") -export class GameLeftSidebar extends LitElement implements Layer { +export class GameLeftSidebar extends LitElement implements Controller { @state() private isLeaderboardShow = false; @state() diff --git a/src/client/graphics/layers/GameRightSidebar.ts b/src/client/graphics/layers/GameRightSidebar.ts index 6770b0658e..02c2d29687 100644 --- a/src/client/graphics/layers/GameRightSidebar.ts +++ b/src/client/graphics/layers/GameRightSidebar.ts @@ -8,8 +8,8 @@ import { crazyGamesSDK } from "../../CrazyGamesSDK"; import { TogglePauseIntentEvent } from "../../InputHandler"; import { PauseGameIntentEvent, SendWinnerEvent } from "../../Transport"; import { translateText } from "../../Utils"; +import { Controller } from "./Controller"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; -import { Layer } from "./Layer"; import { ShowReplayPanelEvent } from "./ReplayPanel"; import { ShowSettingsModalEvent } from "./SettingsModal"; import { SpawnBarVisibleEvent } from "./SpawnTimer"; @@ -22,7 +22,7 @@ const fullscreenIcon = assetUrl("images/FullscreenIconWhite.svg"); const exitFullscreenIcon = assetUrl("images/ExitFullscreenIconWhite.svg"); @customElement("game-right-sidebar") -export class GameRightSidebar extends LitElement implements Layer { +export class GameRightSidebar extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; diff --git a/src/client/graphics/layers/HeadsUpMessage.ts b/src/client/graphics/layers/HeadsUpMessage.ts index 832e5871a3..612cc2022b 100644 --- a/src/client/graphics/layers/HeadsUpMessage.ts +++ b/src/client/graphics/layers/HeadsUpMessage.ts @@ -4,10 +4,10 @@ import { GameType } from "../../../core/game/Game"; import { GameUpdateType } from "../../../core/game/GameUpdates"; import { GameView } from "../../../core/game/GameView"; import { translateText } from "../../Utils"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; @customElement("heads-up-message") -export class HeadsUpMessage extends LitElement implements Layer { +export class HeadsUpMessage extends LitElement implements Controller { public game: GameView; @state() diff --git a/src/client/graphics/layers/ImmunityTimer.ts b/src/client/graphics/layers/ImmunityTimer.ts index d52baa8de0..9f0070e558 100644 --- a/src/client/graphics/layers/ImmunityTimer.ts +++ b/src/client/graphics/layers/ImmunityTimer.ts @@ -3,14 +3,14 @@ import { customElement } from "lit/decorators.js"; import { EventBus, GameEvent } from "../../../core/EventBus"; import { GameMode } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; export class ImmunityBarVisibleEvent implements GameEvent { constructor(public readonly visible: boolean) {} } @customElement("immunity-timer") -export class ImmunityTimer extends LitElement implements Layer { +export class ImmunityTimer extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; @@ -88,10 +88,6 @@ export class ImmunityTimer extends LitElement implements Layer { } } - shouldTransform(): boolean { - return false; - } - render() { if (!this.isVisible || !this.isActive) { return html``; diff --git a/src/client/graphics/layers/InGamePromo.ts b/src/client/graphics/layers/InGamePromo.ts index cc179ca90c..08d0a97e5e 100644 --- a/src/client/graphics/layers/InGamePromo.ts +++ b/src/client/graphics/layers/InGamePromo.ts @@ -2,7 +2,7 @@ import { LitElement, html } from "lit"; import { customElement } from "lit/decorators.js"; import { GameView } from "../../../core/game/GameView"; import { crazyGamesSDK } from "../../CrazyGamesSDK"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const AD_TYPES = [ { type: "standard_iab_left1", selectorId: "in-game-bottom-left-ad" }, @@ -11,7 +11,7 @@ const AD_TYPES = [ ]; @customElement("in-game-promo") -export class InGamePromo extends LitElement implements Layer { +export class InGamePromo extends LitElement implements Controller { public game: GameView; private shouldShow: boolean = false; @@ -169,10 +169,6 @@ export class InGamePromo extends LitElement implements Layer { this.requestUpdate(); } - shouldTransform(): boolean { - return false; - } - render() { if (!this.shouldShow) { return html``; diff --git a/src/client/graphics/layers/Layer.ts b/src/client/graphics/layers/Layer.ts deleted file mode 100644 index 456648f794..0000000000 --- a/src/client/graphics/layers/Layer.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface Layer { - init?: () => void; - tick?: () => void; - // Optional hint to throttle expensive ticks by wall-clock. - // If omitted or <= 0, the layer ticks whenever GameRenderer ticks. - getTickIntervalMs?: () => number; - renderLayer?: (context: CanvasRenderingContext2D) => void; - shouldTransform?: () => boolean; - redraw?: () => void; -} diff --git a/src/client/graphics/layers/Leaderboard.ts b/src/client/graphics/layers/Leaderboard.ts index 3100e88d94..4520e1b7c3 100644 --- a/src/client/graphics/layers/Leaderboard.ts +++ b/src/client/graphics/layers/Leaderboard.ts @@ -6,7 +6,7 @@ import { EventBus } from "../../../core/EventBus"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { formatPercentage, renderNumber } from "../../Utils"; import { GoToPlayerEvent } from "../TransformHandler"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; interface Entry { name: string; @@ -20,7 +20,7 @@ interface Entry { } @customElement("leader-board") -export class Leaderboard extends LitElement implements Layer { +export class Leaderboard extends LitElement implements Controller { public game: GameView | null = null; public eventBus: EventBus | null = null; @@ -157,12 +157,6 @@ export class Leaderboard extends LitElement implements Layer { this.eventBus.emit(new GoToPlayerEvent(player)); } - renderLayer(context: CanvasRenderingContext2D) {} - - shouldTransform(): boolean { - return false; - } - render() { if (!this.visible) { return html``; diff --git a/src/client/graphics/layers/MainRadialMenu.ts b/src/client/graphics/layers/MainRadialMenu.ts index b6adba9291..0d55e2da8d 100644 --- a/src/client/graphics/layers/MainRadialMenu.ts +++ b/src/client/graphics/layers/MainRadialMenu.ts @@ -9,8 +9,8 @@ import { TransformHandler } from "../TransformHandler"; import { UIState } from "../UIState"; import { BuildMenu } from "./BuildMenu"; import { ChatIntegration } from "./ChatIntegration"; +import { Controller } from "./Controller"; import { EmojiTable } from "./EmojiTable"; -import { Layer } from "./Layer"; import { PlayerActionHandler } from "./PlayerActionHandler"; import { PlayerPanel } from "./PlayerPanel"; import { RadialMenu, RadialMenuConfig } from "./RadialMenu"; @@ -26,7 +26,7 @@ const swordIcon = assetUrl("images/SwordIconWhite.svg"); import { ContextMenuEvent } from "../../InputHandler"; @customElement("main-radial-menu") -export class MainRadialMenu extends LitElement implements Layer { +export class MainRadialMenu extends LitElement implements Controller { private radialMenu: RadialMenu; private playerActionHandler: PlayerActionHandler; @@ -173,14 +173,6 @@ export class MainRadialMenu extends LitElement implements Layer { }); } - renderLayer(context: CanvasRenderingContext2D) { - this.radialMenu.renderLayer(context); - } - - shouldTransform(): boolean { - return this.radialMenu.shouldTransform(); - } - closeMenu() { if (this.radialMenu.isMenuVisible()) { this.radialMenu.hideRadialMenu(); diff --git a/src/client/graphics/layers/MultiTabModal.ts b/src/client/graphics/layers/MultiTabModal.ts index f2b5f498e7..90721c00b8 100644 --- a/src/client/graphics/layers/MultiTabModal.ts +++ b/src/client/graphics/layers/MultiTabModal.ts @@ -6,10 +6,10 @@ import { GameType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { MultiTabDetector } from "../../MultiTabDetector"; import { translateText } from "../../Utils"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; @customElement("multi-tab-modal") -export class MultiTabModal extends LitElement implements Layer { +export class MultiTabModal extends LitElement implements Controller { public game: GameView; private detector: MultiTabDetector; diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 9f7fdbe5f4..3686e499c0 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -13,10 +13,10 @@ import { import type { LangSelector } from "../../LangSelector"; import { translateText } from "../../Utils"; import { FrameProfiler } from "../FrameProfiler"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; @customElement("performance-overlay") -export class PerformanceOverlay extends LitElement implements Layer { +export class PerformanceOverlay extends LitElement implements Controller { @property({ type: Object }) public eventBus!: EventBus; @@ -969,10 +969,6 @@ export class PerformanceOverlay extends LitElement implements Layer { } } - shouldTransform(): boolean { - return false; - } - private getPerformanceColor(fps: number): string { if (fps >= 55) return "performance-good"; if (fps >= 30) return "performance-warning"; diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index e720738f4b..7c07d8d616 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -31,8 +31,8 @@ import { IMAGE_ICON_KIND, } from "../PlayerIcons"; import { TransformHandler } from "../TransformHandler"; +import { Controller } from "./Controller"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; -import { Layer } from "./Layer"; import { CloseRadialMenuEvent } from "./RadialMenu"; import "./RelationSmiley"; import { SpawnBarVisibleEvent } from "./SpawnTimer"; @@ -68,7 +68,7 @@ function distSortUnitWorld(coord: { x: number; y: number }, game: GameView) { } @customElement("player-info-overlay") -export class PlayerInfoOverlay extends LitElement implements Layer { +export class PlayerInfoOverlay extends LitElement implements Controller { @property({ type: Object }) public game!: GameView; @@ -171,14 +171,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer { this.requestUpdate(); } - renderLayer(context: CanvasRenderingContext2D) { - // Implementation for Layer interface - } - - shouldTransform(): boolean { - return false; - } - setVisible(visible: boolean) { this._isInfoVisible = visible; this.requestUpdate(); diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index abd86b74b2..0a01dd95b0 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -36,8 +36,8 @@ import { } from "../../Utils"; import { UIState } from "../UIState"; import { ChatModal } from "./ChatModal"; +import { Controller } from "./Controller"; import { EmojiTable } from "./EmojiTable"; -import { Layer } from "./Layer"; import "./PlayerModerationModal"; import "./SendResourceModal"; const allianceIcon = assetUrl("images/AllianceIconWhite.svg"); @@ -53,7 +53,7 @@ const traitorIcon = assetUrl("images/TraitorIconLightRed.svg"); const breakAllianceIcon = assetUrl("images/TraitorIconWhite.svg"); @customElement("player-panel") -export class PlayerPanel extends LitElement implements Layer { +export class PlayerPanel extends LitElement implements Controller { public g: GameView; public eventBus: EventBus; public emojiTable: EmojiTable; diff --git a/src/client/graphics/layers/RadialMenu.ts b/src/client/graphics/layers/RadialMenu.ts index 8e65357e6d..fa6a31015a 100644 --- a/src/client/graphics/layers/RadialMenu.ts +++ b/src/client/graphics/layers/RadialMenu.ts @@ -4,7 +4,7 @@ import { EventBus, GameEvent } from "../../../core/EventBus"; import { CloseViewEvent } from "../../InputHandler"; import { PlaySoundEffectEvent } from "../../sound/Sounds"; import { getSvgAspectRatio, translateText } from "../../Utils"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; import { CenterButtonElement, MenuElement, @@ -51,7 +51,7 @@ type CenterButtonState = "default" | "back"; type RequiredRadialMenuConfig = Required; -export class RadialMenu implements Layer { +export class RadialMenu implements Controller { private menuElement: d3.Selection; private tooltipElement: HTMLDivElement | null = null; private isVisible: boolean = false; @@ -1341,14 +1341,6 @@ export class RadialMenu implements Layer { }); } - renderLayer(context: CanvasRenderingContext2D) { - // No need to render anything on the canvas - } - - shouldTransform(): boolean { - return false; - } - private isReopeningAllowed(): boolean { const now = Date.now(); const timeSinceHide = now - this.lastHideTime; diff --git a/src/client/graphics/layers/ReplayPanel.ts b/src/client/graphics/layers/ReplayPanel.ts index ea48bbb609..79e7b82c50 100644 --- a/src/client/graphics/layers/ReplayPanel.ts +++ b/src/client/graphics/layers/ReplayPanel.ts @@ -8,7 +8,7 @@ import { ReplaySpeedMultiplier, } from "../../utilities/ReplaySpeedMultiplier"; import { translateText } from "../../Utils"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; export class ShowReplayPanelEvent { constructor( @@ -18,7 +18,7 @@ export class ShowReplayPanelEvent { } @customElement("replay-panel") -export class ReplayPanel extends LitElement implements Layer { +export class ReplayPanel extends LitElement implements Controller { public game: GameView | undefined; public eventBus: EventBus | undefined; @@ -65,11 +65,6 @@ export class ReplayPanel extends LitElement implements Layer { this.eventBus?.emit(new ReplaySpeedChangeEvent(value)); } - renderLayer(_ctx: CanvasRenderingContext2D) {} - shouldTransform() { - return false; - } - render() { if (!this.visible) return html``; diff --git a/src/client/graphics/layers/SettingsModal.ts b/src/client/graphics/layers/SettingsModal.ts index 5f747c6baf..22a6cd7c5b 100644 --- a/src/client/graphics/layers/SettingsModal.ts +++ b/src/client/graphics/layers/SettingsModal.ts @@ -11,7 +11,7 @@ import { SetBackgroundMusicVolumeEvent, SetSoundEffectsVolumeEvent, } from "../../sound/Sounds"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const structureIcon = assetUrl("images/CityIconWhite.svg"); const cursorPriceIcon = assetUrl("images/CursorPriceIconWhite.svg"); const darkModeIcon = assetUrl("images/DarkModeIconWhite.svg"); @@ -35,7 +35,7 @@ export class ShowSettingsModalEvent { } @customElement("settings-modal") -export class SettingsModal extends LitElement implements Layer { +export class SettingsModal extends LitElement implements Controller { public eventBus: EventBus; public userSettings: UserSettings; diff --git a/src/client/graphics/layers/SpawnTimer.ts b/src/client/graphics/layers/SpawnTimer.ts index ada1932017..e4e0250525 100644 --- a/src/client/graphics/layers/SpawnTimer.ts +++ b/src/client/graphics/layers/SpawnTimer.ts @@ -4,14 +4,14 @@ import { EventBus, GameEvent } from "../../../core/EventBus"; import { GameMode, GameType, Team } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; export class SpawnBarVisibleEvent implements GameEvent { constructor(public readonly visible: boolean) {} } @customElement("spawn-timer") -export class SpawnTimer extends LitElement implements Layer { +export class SpawnTimer extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; public transformHandler: TransformHandler; @@ -95,10 +95,6 @@ export class SpawnTimer extends LitElement implements Layer { } } - shouldTransform(): boolean { - return false; - } - render() { if (!this.isVisible) { return html``; diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/graphics/layers/StructureIconsLayer.ts index 9c9cbfadba..e6df5c49b2 100644 --- a/src/client/graphics/layers/StructureIconsLayer.ts +++ b/src/client/graphics/layers/StructureIconsLayer.ts @@ -30,14 +30,14 @@ import { } from "../../Transport"; import { TransformHandler } from "../TransformHandler"; import { UIState } from "../UIState"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; /** True for nuke types (AtomBomb, HydrogenBomb): ghost is preserved after placement so user can place multiple or keep selection (Enter/key confirm). */ export function shouldPreserveGhostAfterBuild(unitType: UnitType): boolean { return unitType === UnitType.AtomBomb || unitType === UnitType.HydrogenBomb; } -export class StructureIconsLayer implements Layer { +export class StructureIconsLayer implements Controller { /** Current ghost (null when no build type is active). */ private ghostUnit: { buildableUnit: BuildableUnit } | null = null; private readonly connectedAllySmallIds: Set = new Set(); diff --git a/src/client/graphics/layers/TeamStats.ts b/src/client/graphics/layers/TeamStats.ts index 6e846bcdf6..6ce2c50f54 100644 --- a/src/client/graphics/layers/TeamStats.ts +++ b/src/client/graphics/layers/TeamStats.ts @@ -9,7 +9,7 @@ import { renderTroops, translateText, } from "../../Utils"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; interface TeamEntry { teamName: string; @@ -26,7 +26,7 @@ interface TeamEntry { } @customElement("team-stats") -export class TeamStats extends LitElement implements Layer { +export class TeamStats extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; @@ -125,12 +125,6 @@ export class TeamStats extends LitElement implements Layer { this.requestUpdate(); } - renderLayer(context: CanvasRenderingContext2D) {} - - shouldTransform(): boolean { - return false; - } - render() { if (!this.visible) return html``; diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index d6072d02c0..f8b67eef74 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -16,7 +16,7 @@ import { } from "../../InputHandler"; import { MoveWarshipIntentEvent } from "../../Transport"; import { TransformHandler } from "../TransformHandler"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const WARSHIP_SELECTION_RADIUS = 10; @@ -29,7 +29,7 @@ const WARSHIP_SELECTION_RADIUS = 10; * it stays in the Layer list for lifecycle hooks (init / tick / event * subscriptions). */ -export class UILayer implements Layer { +export class UILayer implements Controller { // Currently selected single warship (game-logic readers use this; the // visual is drawn by WebGL SelectionBoxPass). private selectedUnit: UnitView | null = null; diff --git a/src/client/graphics/layers/UnitDisplay.ts b/src/client/graphics/layers/UnitDisplay.ts index 69c504cb50..456f16530f 100644 --- a/src/client/graphics/layers/UnitDisplay.ts +++ b/src/client/graphics/layers/UnitDisplay.ts @@ -17,7 +17,7 @@ import { } from "../../InputHandler"; import { renderNumber, translateText } from "../../Utils"; import { UIState } from "../UIState"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; const warshipIcon = assetUrl("images/BattleshipIconWhite.svg"); const cityIcon = assetUrl("images/CityIconWhite.svg"); const factoryIcon = assetUrl("images/FactoryIconWhite.svg"); @@ -31,7 +31,7 @@ const samLauncherIcon = assetUrl("images/SamLauncherIconWhite.svg"); const defensePostIcon = assetUrl("images/ShieldIconWhite.svg"); @customElement("unit-display") -export class UnitDisplay extends LitElement implements Layer { +export class UnitDisplay extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; public uiState: UIState; diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index 2c42b5679d..7907ab3173 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -20,10 +20,10 @@ import { import { crazyGamesSDK } from "../../CrazyGamesSDK"; import { Platform } from "../../Platform"; import { SendWinnerEvent } from "../../Transport"; -import { Layer } from "./Layer"; +import { Controller } from "./Controller"; @customElement("win-modal") -export class WinModal extends LitElement implements Layer { +export class WinModal extends LitElement implements Controller { public game: GameView; public eventBus: EventBus; @@ -321,10 +321,4 @@ export class WinModal extends LitElement implements Layer { } }); } - - renderLayer(/* context: CanvasRenderingContext2D */) {} - - shouldTransform(): boolean { - return false; - } } From a708a8c9847a29f8caf23ac79f3493504522917f Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 22:45:02 -0700 Subject: [PATCH 18/34] rename UILayer/StructureIconsLayer to controllers, move to src/client/controllers/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UILayer → WarshipSelectionController and StructureIconsLayer → BuildPreviewController. These are the two real Controller implementations (state + click handling, no rendering) — the new names + location reflect what they actually do now that all rendering lives in WebGL passes. --- .../BuildPreviewController.ts} | 28 ++++++++--------- .../WarshipSelectionController.ts} | 31 ++++++++++--------- src/client/graphics/GameRenderer.ts | 11 +++---- .../BuildPreviewController.test.ts} | 12 ++----- .../WarshipSelectionController.test.ts} | 10 +++--- 5 files changed, 43 insertions(+), 49 deletions(-) rename src/client/{graphics/layers/StructureIconsLayer.ts => controllers/BuildPreviewController.ts} (93%) rename src/client/{graphics/layers/UILayer.ts => controllers/WarshipSelectionController.ts} (91%) rename tests/client/{graphics/layers/StructureIconsLayer.test.ts => controllers/BuildPreviewController.test.ts} (70%) rename tests/client/{graphics/UILayer.test.ts => controllers/WarshipSelectionController.test.ts} (84%) diff --git a/src/client/graphics/layers/StructureIconsLayer.ts b/src/client/controllers/BuildPreviewController.ts similarity index 93% rename from src/client/graphics/layers/StructureIconsLayer.ts rename to src/client/controllers/BuildPreviewController.ts index e6df5c49b2..ef29d3ca2f 100644 --- a/src/client/graphics/layers/StructureIconsLayer.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -1,43 +1,43 @@ /** - * StructureIconsLayer — build-ghost state machine + click-to-build flow. + * BuildPreviewController — build-ghost state machine + click-to-build flow. * * All rendering for the build ghost (outline, range circle, rail snap, - * crosshair) lives in the WebGL renderer. This layer just owns the state: + * crosshair) lives in the WebGL renderer. This controller owns the state: * it queries buildables for the cursor tile, tracks whether the placement * is valid, and emits GhostPreviewUpdatedEvent to feed the renderer. */ -import { EventBus } from "../../../core/EventBus"; -import { wouldNukeBreakAlliance } from "../../../core/execution/Util"; +import { EventBus } from "../../core/EventBus"; +import { wouldNukeBreakAlliance } from "../../core/execution/Util"; import { BuildableUnit, PlayerBuildableUnitType, UnitType, -} from "../../../core/game/Game"; -import { TileRef } from "../../../core/game/GameMap"; -import { GameView } from "../../../core/game/GameView"; +} from "../../core/game/Game"; +import { TileRef } from "../../core/game/GameMap"; +import { GameView } from "../../core/game/GameView"; +import { Controller } from "../graphics/layers/Controller"; +import { TransformHandler } from "../graphics/TransformHandler"; +import { UIState } from "../graphics/UIState"; import { ConfirmGhostStructureEvent, GhostPreviewUpdatedEvent, GhostStructureChangedEvent, MouseMoveEvent, MouseUpEvent, -} from "../../InputHandler"; -import type { GhostPreviewData } from "../../render/types"; +} from "../InputHandler"; +import type { GhostPreviewData } from "../render/types"; import { BuildUnitIntentEvent, SendUpgradeStructureIntentEvent, -} from "../../Transport"; -import { TransformHandler } from "../TransformHandler"; -import { UIState } from "../UIState"; -import { Controller } from "./Controller"; +} from "../Transport"; /** True for nuke types (AtomBomb, HydrogenBomb): ghost is preserved after placement so user can place multiple or keep selection (Enter/key confirm). */ export function shouldPreserveGhostAfterBuild(unitType: UnitType): boolean { return unitType === UnitType.AtomBomb || unitType === UnitType.HydrogenBomb; } -export class StructureIconsLayer implements Controller { +export class BuildPreviewController implements Controller { /** Current ghost (null when no build type is active). */ private ghostUnit: { buildableUnit: BuildableUnit } | null = null; private readonly connectedAllySmallIds: Set = new Set(); diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/controllers/WarshipSelectionController.ts similarity index 91% rename from src/client/graphics/layers/UILayer.ts rename to src/client/controllers/WarshipSelectionController.ts index f8b67eef74..cdf51d4312 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/controllers/WarshipSelectionController.ts @@ -1,8 +1,10 @@ import { Cell } from "src/core/game/Game"; -import { EventBus } from "../../../core/EventBus"; -import { UnitType } from "../../../core/game/Game"; -import { TileRef } from "../../../core/game/GameMap"; -import { GameView, UnitView } from "../../../core/game/GameView"; +import { EventBus } from "../../core/EventBus"; +import { UnitType } from "../../core/game/Game"; +import { TileRef } from "../../core/game/GameMap"; +import { GameView, UnitView } from "../../core/game/GameView"; +import { Controller } from "../graphics/layers/Controller"; +import { TransformHandler } from "../graphics/TransformHandler"; import { CloseViewEvent, ContextMenuEvent, @@ -13,23 +15,24 @@ import { WarshipSelectionBoxCancelEvent, WarshipSelectionBoxCompleteEvent, WarshipSelectionBoxUpdateEvent, -} from "../../InputHandler"; -import { MoveWarshipIntentEvent } from "../../Transport"; -import { TransformHandler } from "../TransformHandler"; -import { Controller } from "./Controller"; +} from "../InputHandler"; +import { MoveWarshipIntentEvent } from "../Transport"; const WARSHIP_SELECTION_RADIUS = 10; /** - * Layer responsible for warship selection state + click handling. + * Controller for warship selection state + click handling. * * Drawing for selection boxes (single + multi) lives in the WebGL - * SelectionBoxPass; the drag-rectangle preview is a screen-space DOM - * overlay (dragRectEl). This layer does not draw to canvas2D at all — - * it stays in the Layer list for lifecycle hooks (init / tick / event - * subscriptions). + * SelectionBoxPass (forwarded via UnitSelectionEvent from ClientGameRunner). + * The drag-rectangle preview is a screen-space DOM overlay (dragRectEl) we + * own here. + * + * This class does not render anything to canvas2D — it's purely a state + + * click controller. The "Controller" pattern: main-thread analog of the + * worker's Execution (init + tick + event subscriptions). */ -export class UILayer implements Controller { +export class WarshipSelectionController implements Controller { // Currently selected single warship (game-logic readers use this; the // visual is drawn by WebGL SelectionBoxPass). private selectedUnit: UnitView | null = null; diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index a1aad6d7f6..9e14f28bc9 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -2,6 +2,8 @@ import { EventBus } from "../../core/EventBus"; import { GameView } from "../../core/game/GameView"; import { UserSettings } from "../../core/game/UserSettings"; import { GameStartingModal } from "../GameStartingModal"; +import { BuildPreviewController } from "../controllers/BuildPreviewController"; +import { WarshipSelectionController } from "../controllers/WarshipSelectionController"; import { FrameProfiler } from "./FrameProfiler"; import { TransformHandler } from "./TransformHandler"; import { UIState } from "./UIState"; @@ -29,9 +31,7 @@ import { PlayerPanel } from "./layers/PlayerPanel"; import { ReplayPanel } from "./layers/ReplayPanel"; import { SettingsModal } from "./layers/SettingsModal"; import { SpawnTimer } from "./layers/SpawnTimer"; -import { StructureIconsLayer } from "./layers/StructureIconsLayer"; import { TeamStats } from "./layers/TeamStats"; -import { UILayer } from "./layers/UILayer"; import { UnitDisplay } from "./layers/UnitDisplay"; import { WinModal } from "./layers/WinModal"; @@ -256,12 +256,9 @@ export function createRenderer( } inGamePromo.game = game; - // When updating these layers please be mindful of the order. - // Try to group layers by the return value of shouldTransform. - // Not grouping the layers may cause excessive calls to context.save() and context.restore(). const layers: Controller[] = [ - new UILayer(game, eventBus, transformHandler), - new StructureIconsLayer(game, eventBus, uiState, transformHandler), + new WarshipSelectionController(game, eventBus, transformHandler), + new BuildPreviewController(game, eventBus, uiState, transformHandler), new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings), eventsDisplay, attacksDisplay, diff --git a/tests/client/graphics/layers/StructureIconsLayer.test.ts b/tests/client/controllers/BuildPreviewController.test.ts similarity index 70% rename from tests/client/graphics/layers/StructureIconsLayer.test.ts rename to tests/client/controllers/BuildPreviewController.test.ts index 7cb8b557cc..3dafc54695 100644 --- a/tests/client/graphics/layers/StructureIconsLayer.test.ts +++ b/tests/client/controllers/BuildPreviewController.test.ts @@ -1,14 +1,8 @@ import { describe, expect, test } from "vitest"; -import { shouldPreserveGhostAfterBuild } from "../../../../src/client/graphics/layers/StructureIconsLayer"; -import { UnitType } from "../../../../src/core/game/Game"; +import { shouldPreserveGhostAfterBuild } from "../../../src/client/controllers/BuildPreviewController"; +import { UnitType } from "../../../src/core/game/Game"; -/** - * Tests for StructureIconsLayer edge cases mentioned in comments: - * - Locked nuke / AtomBomb / HydrogenBomb: when confirming placement (Enter or key), - * the ghost is preserved so the user can place multiple nukes or keep the nuke - * selected. Other structure types clear the ghost after placement. - */ -describe("StructureIconsLayer ghost preservation (locked nuke / Enter confirm)", () => { +describe("BuildPreviewController ghost preservation (locked nuke / Enter confirm)", () => { describe("shouldPreserveGhostAfterBuild", () => { test("returns true for AtomBomb so ghost is not cleared after placement", () => { expect(shouldPreserveGhostAfterBuild(UnitType.AtomBomb)).toBe(true); diff --git a/tests/client/graphics/UILayer.test.ts b/tests/client/controllers/WarshipSelectionController.test.ts similarity index 84% rename from tests/client/graphics/UILayer.test.ts rename to tests/client/controllers/WarshipSelectionController.test.ts index aaafded006..6754d453c2 100644 --- a/tests/client/graphics/UILayer.test.ts +++ b/tests/client/controllers/WarshipSelectionController.test.ts @@ -1,7 +1,7 @@ -import { UILayer } from "../../../src/client/graphics/layers/UILayer"; +import { WarshipSelectionController } from "../../../src/client/controllers/WarshipSelectionController"; import { UnitSelectionEvent } from "../../../src/client/InputHandler"; -describe("UILayer", () => { +describe("WarshipSelectionController", () => { let game: any; let eventBus: any; let transformHandler: any; @@ -29,7 +29,7 @@ describe("UILayer", () => { }); it("tracks the selected unit on single-unit selection (rendering is WebGL)", () => { - const ui = new UILayer(game, eventBus, transformHandler); + const ui = new WarshipSelectionController(game, eventBus, transformHandler); const unit = { type: () => "Warship", isActive: () => true, @@ -45,7 +45,7 @@ describe("UILayer", () => { }); it("clears selection on deselect", () => { - const ui = new UILayer(game, eventBus, transformHandler); + const ui = new WarshipSelectionController(game, eventBus, transformHandler); const unit = { type: () => "Warship", isActive: () => true, @@ -61,7 +61,7 @@ describe("UILayer", () => { }); it("tracks multi-selection list", () => { - const ui = new UILayer(game, eventBus, transformHandler); + const ui = new WarshipSelectionController(game, eventBus, transformHandler); const units = [ { id: () => 1, isActive: () => true }, { id: () => 2, isActive: () => true }, From 7b1557b886c97105370fe7d58f8be76a043b0fe5 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 16 May 2026 22:58:31 -0700 Subject: [PATCH 19/34] controllers push to the WebGL view directly, drop ClientGameRunner relays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BuildPreviewController and WarshipSelectionController now take the WebGL view in their constructor and call view.updateGhostPreview / view.setSelectedUnits themselves instead of emitting bus events that ClientGameRunner forwarded. Splits the old mountWebGLDebugRenderer in two — createWebGLView builds the view up front so the renderer can wire controllers to it, mountWebGLDebugRenderer does the per-frame plumbing after the transformHandler exists. GhostPreviewUpdatedEvent had no remaining consumers and is removed. --- src/client/ClientGameRunner.ts | 75 ++++++++++--------- src/client/InputHandler.ts | 11 --- .../controllers/BuildPreviewController.ts | 18 ++--- .../controllers/WarshipSelectionController.ts | 11 ++- src/client/graphics/GameRenderer.ts | 6 +- .../WarshipSelectionController.test.ts | 25 ++++++- 6 files changed, 81 insertions(+), 65 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 3de721c35b..e347998f9b 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -39,12 +39,10 @@ import { DoGroundAttackEvent, DoRequestAllianceEvent, DoRetaliateAttackEvent, - GhostPreviewUpdatedEvent, InputHandler, MouseMoveEvent, MouseUpEvent, TickMetricsEvent, - UnitSelectionEvent, } from "./InputHandler"; import { endGame, startGame, startTime } from "./LocalPersistantStats"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; @@ -231,12 +229,13 @@ export function joinLobby( }; } -function mountWebGLDebugRenderer( - terrainMap: TerrainMapData, - transformHandler: import("./graphics/TransformHandler").TransformHandler, - gameView: GameView, - eventBus: EventBus, -): { builder: WebGLFrameBuilder } { +// Build the WebGL view + its glCanvas. Must run before createRenderer so the +// controllers can be wired directly to the view. +function createWebGLView(terrainMap: TerrainMapData): { + view: WebGLGameView; + glCanvas: HTMLCanvasElement; + cachedWebGLFrameCallback: { current: FrameRequestCallback | null }; +} { const gameMap = terrainMap.gameMap; const mapWidth = gameMap.width(); const mapHeight = gameMap.height(); @@ -259,13 +258,15 @@ function mountWebGLDebugRenderer( // because its RAF fires before canvas2D's RAF (which would have synced the // camera). Driving WebGL's draw synchronously from canvas2D's onPreRender // hook locks them to the same frame. - let cachedWebGLFrameCallback: FrameRequestCallback | null = null; + const cachedWebGLFrameCallback: { current: FrameRequestCallback | null } = { + current: null, + }; const captureRaf = (cb: FrameRequestCallback): number => { - cachedWebGLFrameCallback = cb; + cachedWebGLFrameCallback.current = cb; return 0; }; const captureCaf = (_id: number): void => { - cachedWebGLFrameCallback = null; + cachedWebGLFrameCallback.current = null; }; const palette = new Float32Array(4096 * 2 * 4); @@ -288,6 +289,24 @@ function mountWebGLDebugRenderer( captureCaf, ); + (window as unknown as { __webglView?: unknown }).__webglView = view; + + return { view, glCanvas, cachedWebGLFrameCallback }; +} + +function mountWebGLDebugRenderer( + terrainMap: TerrainMapData, + view: WebGLGameView, + glCanvas: HTMLCanvasElement, + cachedWebGLFrameCallback: { current: FrameRequestCallback | null }, + transformHandler: import("./graphics/TransformHandler").TransformHandler, + gameView: GameView, + eventBus: EventBus, +): { builder: WebGLFrameBuilder } { + const gameMap = terrainMap.gameMap; + const mapWidth = gameMap.width(); + const mapHeight = gameMap.height(); + window.addEventListener("keydown", (e) => { if (e.key === "\\") { glCanvas.style.display = @@ -326,13 +345,11 @@ function mountWebGLDebugRenderer( // Invoke the WebGL renderer's frame callback synchronously, with the just- // updated camera state. The callback re-arms itself via captureRaf, so // we'll get a fresh callback ready for the next canvas2D frame. - const cb = cachedWebGLFrameCallback; - cachedWebGLFrameCallback = null; + const cb = cachedWebGLFrameCallback.current; + cachedWebGLFrameCallback.current = null; cb?.(performance.now()); }; - (window as unknown as { __webglView?: unknown }).__webglView = view; - // Move-target chevrons: when the player issues a warship move, show the // animated chevron pass at the target tile. The renderer needs the target's // tile x/y and the warship's owner smallID (so the chevrons use the right @@ -347,27 +364,6 @@ function mountWebGLDebugRenderer( view.showMoveIndicator(tx, ty, firstUnit.owner().smallID()); }); - // Build-mode ghost preview: forward the per-frame state to the renderer's - // ghost passes (structure outline, range circle, rail snap, crosshair). - eventBus.on(GhostPreviewUpdatedEvent, (e) => { - view.updateGhostPreview(e.data); - }); - - // Warship selection boxes: forward UnitSelectionEvent to the renderer's - // SelectionBoxPass for both single and multi selections. - eventBus.on(UnitSelectionEvent, (e) => { - if (!e.isSelected) { - view.setSelectedUnits([]); - return; - } - const multi = e.units ?? []; - if (multi.length > 0) { - view.setSelectedUnits(multi.map((u) => u.id())); - return; - } - view.setSelectedUnits(e.unit ? [e.unit.id()] : []); - }); - // Self-driving RAF: syncCamera reads the latest camera state from // TransformHandler, pushes it to WebGL, and synchronously invokes the // renderer's captured frame callback (which draws). One RAF = one @@ -437,15 +433,22 @@ async function createClientGame( const soundManager = new SoundManager(eventBus, userSettings); try { + const { view, glCanvas, cachedWebGLFrameCallback } = + createWebGLView(gameMap); + const gameRenderer = createRenderer( inputOverlay, gameView, eventBus, lobbyConfig.playerRole, + view, ); const { builder: webglBuilder } = mountWebGLDebugRenderer( gameMap, + view, + glCanvas, + cachedWebGLFrameCallback, gameRenderer.transformHandler, gameView, eventBus, diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index 8a30e150e1..4dd3cc36a9 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -96,17 +96,6 @@ export class GhostStructureChangedEvent implements GameEvent { constructor(public readonly ghostStructure: PlayerBuildableUnitType | null) {} } -/** - * Per-frame ghost preview state for the WebGL renderer. Emitted by the - * canvas2D ghost layer; consumed in ClientGameRunner.mountWebGLDebugRenderer - * to push to view.updateGhostPreview. `data` is null when no ghost is active. - */ -export class GhostPreviewUpdatedEvent implements GameEvent { - constructor( - public readonly data: import("./render/types").GhostPreviewData | null, - ) {} -} - export class ConfirmGhostStructureEvent implements GameEvent {} export class SwapRocketDirectionEvent implements GameEvent { diff --git a/src/client/controllers/BuildPreviewController.ts b/src/client/controllers/BuildPreviewController.ts index ef29d3ca2f..17275c8f19 100644 --- a/src/client/controllers/BuildPreviewController.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -4,7 +4,7 @@ * All rendering for the build ghost (outline, range circle, rail snap, * crosshair) lives in the WebGL renderer. This controller owns the state: * it queries buildables for the cursor tile, tracks whether the placement - * is valid, and emits GhostPreviewUpdatedEvent to feed the renderer. + * is valid, and pushes preview data straight to the WebGL view. */ import { EventBus } from "../../core/EventBus"; @@ -21,11 +21,11 @@ import { TransformHandler } from "../graphics/TransformHandler"; import { UIState } from "../graphics/UIState"; import { ConfirmGhostStructureEvent, - GhostPreviewUpdatedEvent, GhostStructureChangedEvent, MouseMoveEvent, MouseUpEvent, } from "../InputHandler"; +import { GameView as WebGLGameView } from "../render/gl"; import type { GhostPreviewData } from "../render/types"; import { BuildUnitIntentEvent, @@ -50,6 +50,7 @@ export class BuildPreviewController implements Controller { private eventBus: EventBus, public uiState: UIState, private transformHandler: TransformHandler, + private view: WebGLGameView, ) {} init() { @@ -183,15 +184,12 @@ export class BuildPreviewController implements Controller { } /** - * Build a GhostPreviewData snapshot from the current ghost state and emit - * it for the WebGL renderer to consume (StructurePass / RangeCirclePass / - * RailroadPass / CrosshairPass all read it via view.updateGhostPreview). - * Emits null when the ghost can't be placed. + * Push a GhostPreviewData snapshot to the WebGL view (StructurePass / + * RangeCirclePass / RailroadPass / CrosshairPass all read it). null when + * the ghost can't be placed. */ private emitGhostPreview(tileRef: TileRef | undefined): void { - this.eventBus.emit( - new GhostPreviewUpdatedEvent(this.buildGhostPreviewData(tileRef)), - ); + this.view.updateGhostPreview(this.buildGhostPreviewData(tileRef)); } private buildGhostPreviewData( @@ -310,7 +308,7 @@ export class BuildPreviewController implements Controller { this.pendingConfirm = null; this.ghostUnit = null; this.uiState.ghostRailPaths = []; - this.eventBus.emit(new GhostPreviewUpdatedEvent(null)); + this.view.updateGhostPreview(null); } private removeGhostStructure() { diff --git a/src/client/controllers/WarshipSelectionController.ts b/src/client/controllers/WarshipSelectionController.ts index cdf51d4312..d8e01541a2 100644 --- a/src/client/controllers/WarshipSelectionController.ts +++ b/src/client/controllers/WarshipSelectionController.ts @@ -16,6 +16,7 @@ import { WarshipSelectionBoxCompleteEvent, WarshipSelectionBoxUpdateEvent, } from "../InputHandler"; +import { GameView as WebGLGameView } from "../render/gl"; import { MoveWarshipIntentEvent } from "../Transport"; const WARSHIP_SELECTION_RADIUS = 10; @@ -47,6 +48,7 @@ export class WarshipSelectionController implements Controller { private game: GameView, private eventBus: EventBus, private transformHandler: TransformHandler, + private view: WebGLGameView, ) {} tick() { @@ -295,17 +297,20 @@ export class WarshipSelectionController implements Controller { * When event.isSelected is false it clears all selection state. */ private onUnitSelection(event: UnitSelectionEvent) { - // Selection box visuals are drawn by the WebGL SelectionBoxPass; this - // method just tracks selection state for the click-handler logic. this.multiSelectedWarships = []; this.selectedUnit = null; - if (!event.isSelected) return; + if (!event.isSelected) { + this.view.setSelectedUnits([]); + return; + } if ((event.units ?? []).length > 0) { this.multiSelectedWarships = event.units; + this.view.setSelectedUnits(event.units.map((u) => u.id())); } else { this.selectedUnit = event.unit; + this.view.setSelectedUnits(event.unit ? [event.unit.id()] : []); } } } diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 9e14f28bc9..03ed58346a 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -4,6 +4,7 @@ import { UserSettings } from "../../core/game/UserSettings"; import { GameStartingModal } from "../GameStartingModal"; import { BuildPreviewController } from "../controllers/BuildPreviewController"; import { WarshipSelectionController } from "../controllers/WarshipSelectionController"; +import { GameView as WebGLGameView } from "../render/gl"; import { FrameProfiler } from "./FrameProfiler"; import { TransformHandler } from "./TransformHandler"; import { UIState } from "./UIState"; @@ -40,6 +41,7 @@ export function createRenderer( game: GameView, eventBus: EventBus, playerRole: string | null, + view: WebGLGameView, ): GameRenderer { const transformHandler = new TransformHandler(game, eventBus, inputEl); const userSettings = new UserSettings(); @@ -257,8 +259,8 @@ export function createRenderer( inGamePromo.game = game; const layers: Controller[] = [ - new WarshipSelectionController(game, eventBus, transformHandler), - new BuildPreviewController(game, eventBus, uiState, transformHandler), + new WarshipSelectionController(game, eventBus, transformHandler, view), + new BuildPreviewController(game, eventBus, uiState, transformHandler, view), new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings), eventsDisplay, attacksDisplay, diff --git a/tests/client/controllers/WarshipSelectionController.test.ts b/tests/client/controllers/WarshipSelectionController.test.ts index 6754d453c2..a28aef4a18 100644 --- a/tests/client/controllers/WarshipSelectionController.test.ts +++ b/tests/client/controllers/WarshipSelectionController.test.ts @@ -5,6 +5,7 @@ describe("WarshipSelectionController", () => { let game: any; let eventBus: any; let transformHandler: any; + let view: any; beforeEach(() => { game = { @@ -26,11 +27,18 @@ describe("WarshipSelectionController", () => { }; eventBus = { on: vi.fn() }; transformHandler = {}; + view = { setSelectedUnits: vi.fn() }; }); it("tracks the selected unit on single-unit selection (rendering is WebGL)", () => { - const ui = new WarshipSelectionController(game, eventBus, transformHandler); + const ui = new WarshipSelectionController( + game, + eventBus, + transformHandler, + view, + ); const unit = { + id: () => 1, type: () => "Warship", isActive: () => true, tile: () => ({}), @@ -45,8 +53,14 @@ describe("WarshipSelectionController", () => { }); it("clears selection on deselect", () => { - const ui = new WarshipSelectionController(game, eventBus, transformHandler); + const ui = new WarshipSelectionController( + game, + eventBus, + transformHandler, + view, + ); const unit = { + id: () => 1, type: () => "Warship", isActive: () => true, tile: () => ({}), @@ -61,7 +75,12 @@ describe("WarshipSelectionController", () => { }); it("tracks multi-selection list", () => { - const ui = new WarshipSelectionController(game, eventBus, transformHandler); + const ui = new WarshipSelectionController( + game, + eventBus, + transformHandler, + view, + ); const units = [ { id: () => 1, isActive: () => true }, { id: () => 2, isActive: () => true }, From eb046e5a582dae55934269574ba424f4482f7930 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 12:24:41 -0700 Subject: [PATCH 20/34] move TransformHandler/UIState/Controller out of graphics/, drop dead GhostStructureChangedEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit graphics/ was a canvas2D-era directory name — TransformHandler, UIState, and the Controller interface aren't graphics, they're cross-cutting client state. Hoist them to src/client/ so the path matches what they are. GhostStructureChangedEvent had three emitters and zero listeners; removed. --- src/client/ClientGameRunner.ts | 4 ++-- src/client/{graphics/layers => }/Controller.ts | 0 src/client/InputHandler.ts | 7 +------ src/client/{graphics => }/TransformHandler.ts | 8 ++++---- src/client/{graphics => }/UIState.ts | 4 ++-- src/client/controllers/BuildPreviewController.ts | 8 +++----- .../controllers/WarshipSelectionController.ts | 4 ++-- src/client/graphics/GameRenderer.ts | 6 +++--- src/client/graphics/layers/AlertFrame.ts | 2 +- .../graphics/layers/AttackingTroopsOverlay.ts | 4 ++-- src/client/graphics/layers/AttacksDisplay.ts | 14 +++++++------- src/client/graphics/layers/BuildMenu.ts | 6 +++--- src/client/graphics/layers/ChatDisplay.ts | 2 +- src/client/graphics/layers/ControlPanel.ts | 4 ++-- src/client/graphics/layers/EmojiTable.ts | 2 +- src/client/graphics/layers/EventsDisplay.ts | 6 +++--- src/client/graphics/layers/GameLeftSidebar.ts | 2 +- src/client/graphics/layers/GameRightSidebar.ts | 2 +- src/client/graphics/layers/HeadsUpMessage.ts | 2 +- src/client/graphics/layers/ImmunityTimer.ts | 2 +- src/client/graphics/layers/InGamePromo.ts | 2 +- src/client/graphics/layers/Leaderboard.ts | 4 ++-- src/client/graphics/layers/MainRadialMenu.ts | 6 +++--- src/client/graphics/layers/MultiTabModal.ts | 2 +- src/client/graphics/layers/PerformanceOverlay.ts | 2 +- src/client/graphics/layers/PlayerActionHandler.ts | 2 +- src/client/graphics/layers/PlayerInfoOverlay.ts | 4 ++-- src/client/graphics/layers/PlayerPanel.ts | 4 ++-- src/client/graphics/layers/RadialMenu.ts | 2 +- src/client/graphics/layers/RadialMenuElements.ts | 2 +- src/client/graphics/layers/ReplayPanel.ts | 2 +- src/client/graphics/layers/SendResourceModal.ts | 2 +- src/client/graphics/layers/SettingsModal.ts | 2 +- src/client/graphics/layers/SpawnTimer.ts | 4 ++-- .../graphics/layers/StructureDrawingUtils.ts | 2 +- src/client/graphics/layers/TeamStats.ts | 2 +- src/client/graphics/layers/UnitDisplay.ts | 11 +++-------- src/client/graphics/layers/WinModal.ts | 2 +- src/client/graphics/ui/TextIndicator.ts | 2 +- tests/InputHandler.test.ts | 5 +---- 40 files changed, 69 insertions(+), 84 deletions(-) rename src/client/{graphics/layers => }/Controller.ts (100%) rename src/client/{graphics => }/TransformHandler.ts (97%) rename src/client/{graphics => }/UIState.ts (63%) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index e347998f9b..505abf8617 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -46,6 +46,7 @@ import { } from "./InputHandler"; import { endGame, startGame, startTime } from "./LocalPersistantStats"; import { terrainMapFileLoader } from "./TerrainMapFileLoader"; +import { GoToPlayerEvent } from "./TransformHandler"; import { MoveWarshipIntentEvent, SendAllianceExtensionIntentEvent, @@ -61,7 +62,6 @@ import { import { createCanvas } from "./Utils"; import { WebGLFrameBuilder } from "./WebGLFrameBuilder"; import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; -import { GoToPlayerEvent } from "./graphics/TransformHandler"; import { GameView as WebGLGameView } from "./render/gl"; import { ALL_UNIT_TYPES } from "./render/types"; import { SoundManager } from "./sound/SoundManager"; @@ -299,7 +299,7 @@ function mountWebGLDebugRenderer( view: WebGLGameView, glCanvas: HTMLCanvasElement, cachedWebGLFrameCallback: { current: FrameRequestCallback | null }, - transformHandler: import("./graphics/TransformHandler").TransformHandler, + transformHandler: import("./TransformHandler").TransformHandler, gameView: GameView, eventBus: EventBus, ): { builder: WebGLFrameBuilder } { diff --git a/src/client/graphics/layers/Controller.ts b/src/client/Controller.ts similarity index 100% rename from src/client/graphics/layers/Controller.ts rename to src/client/Controller.ts diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index 4dd3cc36a9..d0cc7334d2 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -2,8 +2,8 @@ import { EventBus, GameEvent } from "../core/EventBus"; import { PlayerBuildableUnitType, UnitType } from "../core/game/Game"; import { GameView, UnitView } from "../core/game/GameView"; import { UserSettings } from "../core/game/UserSettings"; -import { UIState } from "./graphics/UIState"; import { Platform } from "./Platform"; +import { UIState } from "./UIState"; import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier"; export class MouseUpEvent implements GameEvent { @@ -92,10 +92,6 @@ export class ToggleStructureEvent implements GameEvent { ) {} } -export class GhostStructureChangedEvent implements GameEvent { - constructor(public readonly ghostStructure: PlayerBuildableUnitType | null) {} -} - export class ConfirmGhostStructureEvent implements GameEvent {} export class SwapRocketDirectionEvent implements GameEvent { @@ -850,7 +846,6 @@ export class InputHandler { private setGhostStructure(ghostStructure: PlayerBuildableUnitType | null) { this.uiState.ghostStructure = ghostStructure; - this.eventBus.emit(new GhostStructureChangedEvent(ghostStructure)); } /** diff --git a/src/client/graphics/TransformHandler.ts b/src/client/TransformHandler.ts similarity index 97% rename from src/client/graphics/TransformHandler.ts rename to src/client/TransformHandler.ts index 5e69a6901e..c1175b7882 100644 --- a/src/client/graphics/TransformHandler.ts +++ b/src/client/TransformHandler.ts @@ -1,7 +1,7 @@ -import { EventBus, GameEvent } from "../../core/EventBus"; -import { Cell } from "../../core/game/Game"; -import { GameView, PlayerView, UnitView } from "../../core/game/GameView"; -import { CenterCameraEvent, DragEvent, ZoomEvent } from "../InputHandler"; +import { EventBus, GameEvent } from "../core/EventBus"; +import { Cell } from "../core/game/Game"; +import { GameView, PlayerView, UnitView } from "../core/game/GameView"; +import { CenterCameraEvent, DragEvent, ZoomEvent } from "./InputHandler"; export class GoToPlayerEvent implements GameEvent { constructor( diff --git a/src/client/graphics/UIState.ts b/src/client/UIState.ts similarity index 63% rename from src/client/graphics/UIState.ts rename to src/client/UIState.ts index c43a773f1d..c4f5296156 100644 --- a/src/client/graphics/UIState.ts +++ b/src/client/UIState.ts @@ -1,5 +1,5 @@ -import { PlayerBuildableUnitType } from "../../core/game/Game"; -import { TileRef } from "../../core/game/GameMap"; +import { PlayerBuildableUnitType } from "../core/game/Game"; +import { TileRef } from "../core/game/GameMap"; export interface UIState { attackRatio: number; diff --git a/src/client/controllers/BuildPreviewController.ts b/src/client/controllers/BuildPreviewController.ts index 17275c8f19..21a9398a16 100644 --- a/src/client/controllers/BuildPreviewController.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -16,21 +16,20 @@ import { } from "../../core/game/Game"; import { TileRef } from "../../core/game/GameMap"; import { GameView } from "../../core/game/GameView"; -import { Controller } from "../graphics/layers/Controller"; -import { TransformHandler } from "../graphics/TransformHandler"; -import { UIState } from "../graphics/UIState"; +import { Controller } from "../Controller"; import { ConfirmGhostStructureEvent, - GhostStructureChangedEvent, MouseMoveEvent, MouseUpEvent, } from "../InputHandler"; import { GameView as WebGLGameView } from "../render/gl"; import type { GhostPreviewData } from "../render/types"; +import { TransformHandler } from "../TransformHandler"; import { BuildUnitIntentEvent, SendUpgradeStructureIntentEvent, } from "../Transport"; +import { UIState } from "../UIState"; /** True for nuke types (AtomBomb, HydrogenBomb): ghost is preserved after placement so user can place multiple or keep selection (Enter/key confirm). */ export function shouldPreserveGhostAfterBuild(unitType: UnitType): boolean { @@ -314,7 +313,6 @@ export class BuildPreviewController implements Controller { private removeGhostStructure() { this.clearGhostStructure(); this.uiState.ghostStructure = null; - this.eventBus.emit(new GhostStructureChangedEvent(null)); } private resolveGhostRangeLevel( diff --git a/src/client/controllers/WarshipSelectionController.ts b/src/client/controllers/WarshipSelectionController.ts index d8e01541a2..fdaa9294b1 100644 --- a/src/client/controllers/WarshipSelectionController.ts +++ b/src/client/controllers/WarshipSelectionController.ts @@ -3,8 +3,7 @@ import { EventBus } from "../../core/EventBus"; import { UnitType } from "../../core/game/Game"; import { TileRef } from "../../core/game/GameMap"; import { GameView, UnitView } from "../../core/game/GameView"; -import { Controller } from "../graphics/layers/Controller"; -import { TransformHandler } from "../graphics/TransformHandler"; +import { Controller } from "../Controller"; import { CloseViewEvent, ContextMenuEvent, @@ -17,6 +16,7 @@ import { WarshipSelectionBoxUpdateEvent, } from "../InputHandler"; import { GameView as WebGLGameView } from "../render/gl"; +import { TransformHandler } from "../TransformHandler"; import { MoveWarshipIntentEvent } from "../Transport"; const WARSHIP_SELECTION_RADIUS = 10; diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 03ed58346a..c8c854a678 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -1,13 +1,14 @@ import { EventBus } from "../../core/EventBus"; import { GameView } from "../../core/game/GameView"; import { UserSettings } from "../../core/game/UserSettings"; +import { Controller } from "../Controller"; import { GameStartingModal } from "../GameStartingModal"; +import { TransformHandler } from "../TransformHandler"; +import { UIState } from "../UIState"; import { BuildPreviewController } from "../controllers/BuildPreviewController"; import { WarshipSelectionController } from "../controllers/WarshipSelectionController"; import { GameView as WebGLGameView } from "../render/gl"; import { FrameProfiler } from "./FrameProfiler"; -import { TransformHandler } from "./TransformHandler"; -import { UIState } from "./UIState"; import { AlertFrame } from "./layers/AlertFrame"; import { AttackingTroopsOverlay } from "./layers/AttackingTroopsOverlay"; import { AttacksDisplay } from "./layers/AttacksDisplay"; @@ -15,7 +16,6 @@ import { BuildMenu } from "./layers/BuildMenu"; import { ChatDisplay } from "./layers/ChatDisplay"; import { ChatModal } from "./layers/ChatModal"; import { ControlPanel } from "./layers/ControlPanel"; -import { Controller } from "./layers/Controller"; import { EmojiTable } from "./layers/EmojiTable"; import { EventsDisplay } from "./layers/EventsDisplay"; import { GameLeftSidebar } from "./layers/GameLeftSidebar"; diff --git a/src/client/graphics/layers/AlertFrame.ts b/src/client/graphics/layers/AlertFrame.ts index 168c9ee0a2..fcadb4969d 100644 --- a/src/client/graphics/layers/AlertFrame.ts +++ b/src/client/graphics/layers/AlertFrame.ts @@ -7,7 +7,7 @@ import { } from "../../../core/game/GameUpdates"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; -import { Controller } from "./Controller"; +import { Controller } from "../../Controller"; // Parameters for the alert animation const ALERT_SPEED = 1.6; diff --git a/src/client/graphics/layers/AttackingTroopsOverlay.ts b/src/client/graphics/layers/AttackingTroopsOverlay.ts index bc88f90da7..bcc4342019 100644 --- a/src/client/graphics/layers/AttackingTroopsOverlay.ts +++ b/src/client/graphics/layers/AttackingTroopsOverlay.ts @@ -2,10 +2,10 @@ import { EventBus } from "../../../core/EventBus"; import { Cell, PlayerType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; +import { Controller } from "../../Controller"; import { AlternateViewEvent } from "../../InputHandler"; +import { TransformHandler } from "../../TransformHandler"; import { renderTroops } from "../../Utils"; -import { TransformHandler } from "../TransformHandler"; -import { Controller } from "./Controller"; // Match AttacksDisplay: aquarius for outgoing, red-400 for incoming. const OUTGOING_COLOR = "var(--color-aquarius)"; diff --git a/src/client/graphics/layers/AttacksDisplay.ts b/src/client/graphics/layers/AttacksDisplay.ts index 82d0e1c92a..13e4fc04f7 100644 --- a/src/client/graphics/layers/AttacksDisplay.ts +++ b/src/client/graphics/layers/AttacksDisplay.ts @@ -9,20 +9,20 @@ import { UnitIncomingUpdate, } from "../../../core/game/GameUpdates"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; +import { + GoToPlayerEvent, + GoToPositionEvent, + GoToUnitEvent, +} from "../../TransformHandler"; import { CancelAttackIntentEvent, CancelBoatIntentEvent, SendAttackIntentEvent, } from "../../Transport"; +import { UIState } from "../../UIState"; import { renderTroops, translateText } from "../../Utils"; import { getColoredSprite } from "../SpriteLoader"; -import { - GoToPlayerEvent, - GoToPositionEvent, - GoToUnitEvent, -} from "../TransformHandler"; -import { UIState } from "../UIState"; -import { Controller } from "./Controller"; const soldierIcon = assetUrl("images/SoldierIcon.svg"); const swordIcon = assetUrl("images/SwordIcon.svg"); diff --git a/src/client/graphics/layers/BuildMenu.ts b/src/client/graphics/layers/BuildMenu.ts index e9cb0c5cb4..46d1a8f41d 100644 --- a/src/client/graphics/layers/BuildMenu.ts +++ b/src/client/graphics/layers/BuildMenu.ts @@ -12,20 +12,20 @@ import { } from "../../../core/game/Game"; import { TileRef } from "../../../core/game/GameMap"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { CloseViewEvent, MouseDownEvent, ShowBuildMenuEvent, ShowEmojiMenuEvent, } from "../../InputHandler"; +import { TransformHandler } from "../../TransformHandler"; import { BuildUnitIntentEvent, SendUpgradeStructureIntentEvent, } from "../../Transport"; +import { UIState } from "../../UIState"; import { renderNumber } from "../../Utils"; -import { TransformHandler } from "../TransformHandler"; -import { UIState } from "../UIState"; -import { Controller } from "./Controller"; const warshipIcon = assetUrl("images/BattleshipIconWhite.svg"); const cityIcon = assetUrl("images/CityIconWhite.svg"); const factoryIcon = assetUrl("images/FactoryIconWhite.svg"); diff --git a/src/client/graphics/layers/ChatDisplay.ts b/src/client/graphics/layers/ChatDisplay.ts index 2a7514c234..04018b2cfc 100644 --- a/src/client/graphics/layers/ChatDisplay.ts +++ b/src/client/graphics/layers/ChatDisplay.ts @@ -10,7 +10,7 @@ import { } from "../../../core/game/GameUpdates"; import { GameView } from "../../../core/game/GameView"; import { onlyImages } from "../../../core/Util"; -import { Controller } from "./Controller"; +import { Controller } from "../../Controller"; interface ChatEvent { description: string; diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts index 480be247ca..dce219b5ad 100644 --- a/src/client/graphics/layers/ControlPanel.ts +++ b/src/client/graphics/layers/ControlPanel.ts @@ -6,10 +6,10 @@ import { Gold } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; import { ClientID } from "../../../core/Schemas"; +import { Controller } from "../../Controller"; import { AttackRatioEvent } from "../../InputHandler"; +import { UIState } from "../../UIState"; import { renderNumber, renderTroops } from "../../Utils"; -import { UIState } from "../UIState"; -import { Controller } from "./Controller"; const goldCoinIcon = assetUrl("images/GoldCoinIcon.svg"); const soldierIcon = assetUrl("images/SoldierIcon.svg"); const swordIcon = assetUrl("images/SwordIcon.svg"); diff --git a/src/client/graphics/layers/EmojiTable.ts b/src/client/graphics/layers/EmojiTable.ts index ef3547e41b..787b837257 100644 --- a/src/client/graphics/layers/EmojiTable.ts +++ b/src/client/graphics/layers/EmojiTable.ts @@ -6,8 +6,8 @@ import { GameView, PlayerView } from "../../../core/game/GameView"; import { TerraNulliusImpl } from "../../../core/game/TerraNulliusImpl"; import { Emoji, flattenedEmojiTable } from "../../../core/Util"; import { CloseViewEvent, ShowEmojiMenuEvent } from "../../InputHandler"; +import { TransformHandler } from "../../TransformHandler"; import { SendEmojiIntentEvent } from "../../Transport"; -import { TransformHandler } from "../TransformHandler"; @customElement("emoji-table") export class EmojiTable extends LitElement { diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 3516e1ca4c..96264656b9 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -24,21 +24,21 @@ import { TargetPlayerUpdate, UnitIncomingUpdate, } from "../../../core/game/GameUpdates"; +import { Controller } from "../../Controller"; import { SendAllianceExtensionIntentEvent, SendAllianceRejectIntentEvent, SendAllianceRequestIntentEvent, } from "../../Transport"; -import { Controller } from "./Controller"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; import { onlyImages } from "../../../core/Util"; +import { GoToPlayerEvent, GoToUnitEvent } from "../../TransformHandler"; import { renderNumber } from "../../Utils"; -import { GoToPlayerEvent, GoToUnitEvent } from "../TransformHandler"; import { PlaySoundEffectEvent } from "../../sound/Sounds"; +import { UIState } from "../../UIState"; import { getMessageTypeClasses, translateText } from "../../Utils"; -import { UIState } from "../UIState"; const allianceIcon = assetUrl("images/AllianceIconWhite.svg"); const chatIcon = assetUrl("images/ChatIconWhite.svg"); const donateGoldIcon = assetUrl("images/DonateGoldIconWhite.svg"); diff --git a/src/client/graphics/layers/GameLeftSidebar.ts b/src/client/graphics/layers/GameLeftSidebar.ts index 296e057d49..a0039e327e 100644 --- a/src/client/graphics/layers/GameLeftSidebar.ts +++ b/src/client/graphics/layers/GameLeftSidebar.ts @@ -5,9 +5,9 @@ import { assetUrl } from "../../../core/AssetUrls"; import { EventBus } from "../../../core/EventBus"; import { GameMode, Team } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { Platform } from "../../Platform"; import { getTranslatedPlayerTeamLabel, translateText } from "../../Utils"; -import { Controller } from "./Controller"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; import { SpawnBarVisibleEvent } from "./SpawnTimer"; const leaderboardRegularIcon = assetUrl( diff --git a/src/client/graphics/layers/GameRightSidebar.ts b/src/client/graphics/layers/GameRightSidebar.ts index 02c2d29687..47750465dd 100644 --- a/src/client/graphics/layers/GameRightSidebar.ts +++ b/src/client/graphics/layers/GameRightSidebar.ts @@ -4,11 +4,11 @@ import { assetUrl } from "../../../core/AssetUrls"; import { EventBus } from "../../../core/EventBus"; import { GameType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { crazyGamesSDK } from "../../CrazyGamesSDK"; import { TogglePauseIntentEvent } from "../../InputHandler"; import { PauseGameIntentEvent, SendWinnerEvent } from "../../Transport"; import { translateText } from "../../Utils"; -import { Controller } from "./Controller"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; import { ShowReplayPanelEvent } from "./ReplayPanel"; import { ShowSettingsModalEvent } from "./SettingsModal"; diff --git a/src/client/graphics/layers/HeadsUpMessage.ts b/src/client/graphics/layers/HeadsUpMessage.ts index 612cc2022b..7f9b95532d 100644 --- a/src/client/graphics/layers/HeadsUpMessage.ts +++ b/src/client/graphics/layers/HeadsUpMessage.ts @@ -3,8 +3,8 @@ import { customElement, state } from "lit/decorators.js"; import { GameType } from "../../../core/game/Game"; import { GameUpdateType } from "../../../core/game/GameUpdates"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { translateText } from "../../Utils"; -import { Controller } from "./Controller"; @customElement("heads-up-message") export class HeadsUpMessage extends LitElement implements Controller { diff --git a/src/client/graphics/layers/ImmunityTimer.ts b/src/client/graphics/layers/ImmunityTimer.ts index 9f0070e558..4cfefb99c0 100644 --- a/src/client/graphics/layers/ImmunityTimer.ts +++ b/src/client/graphics/layers/ImmunityTimer.ts @@ -3,7 +3,7 @@ import { customElement } from "lit/decorators.js"; import { EventBus, GameEvent } from "../../../core/EventBus"; import { GameMode } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; -import { Controller } from "./Controller"; +import { Controller } from "../../Controller"; export class ImmunityBarVisibleEvent implements GameEvent { constructor(public readonly visible: boolean) {} diff --git a/src/client/graphics/layers/InGamePromo.ts b/src/client/graphics/layers/InGamePromo.ts index 08d0a97e5e..d26efb85dd 100644 --- a/src/client/graphics/layers/InGamePromo.ts +++ b/src/client/graphics/layers/InGamePromo.ts @@ -1,8 +1,8 @@ import { LitElement, html } from "lit"; import { customElement } from "lit/decorators.js"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { crazyGamesSDK } from "../../CrazyGamesSDK"; -import { Controller } from "./Controller"; const AD_TYPES = [ { type: "standard_iab_left1", selectorId: "in-game-bottom-left-ad" }, diff --git a/src/client/graphics/layers/Leaderboard.ts b/src/client/graphics/layers/Leaderboard.ts index 4520e1b7c3..4a805ca20a 100644 --- a/src/client/graphics/layers/Leaderboard.ts +++ b/src/client/graphics/layers/Leaderboard.ts @@ -4,9 +4,9 @@ import { repeat } from "lit/directives/repeat.js"; import { renderTroops, translateText } from "../../../client/Utils"; import { EventBus } from "../../../core/EventBus"; import { GameView, PlayerView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; +import { GoToPlayerEvent } from "../../TransformHandler"; import { formatPercentage, renderNumber } from "../../Utils"; -import { GoToPlayerEvent } from "../TransformHandler"; -import { Controller } from "./Controller"; interface Entry { name: string; diff --git a/src/client/graphics/layers/MainRadialMenu.ts b/src/client/graphics/layers/MainRadialMenu.ts index 0d55e2da8d..c615db0d02 100644 --- a/src/client/graphics/layers/MainRadialMenu.ts +++ b/src/client/graphics/layers/MainRadialMenu.ts @@ -5,11 +5,11 @@ import { EventBus } from "../../../core/EventBus"; import { PlayerActions } from "../../../core/game/Game"; import { TileRef } from "../../../core/game/GameMap"; import { GameView, PlayerView } from "../../../core/game/GameView"; -import { TransformHandler } from "../TransformHandler"; -import { UIState } from "../UIState"; +import { Controller } from "../../Controller"; +import { TransformHandler } from "../../TransformHandler"; +import { UIState } from "../../UIState"; import { BuildMenu } from "./BuildMenu"; import { ChatIntegration } from "./ChatIntegration"; -import { Controller } from "./Controller"; import { EmojiTable } from "./EmojiTable"; import { PlayerActionHandler } from "./PlayerActionHandler"; import { PlayerPanel } from "./PlayerPanel"; diff --git a/src/client/graphics/layers/MultiTabModal.ts b/src/client/graphics/layers/MultiTabModal.ts index 90721c00b8..1ea12914bb 100644 --- a/src/client/graphics/layers/MultiTabModal.ts +++ b/src/client/graphics/layers/MultiTabModal.ts @@ -4,9 +4,9 @@ import { ClientEnv } from "src/client/ClientEnv"; import { GameEnv } from "../../../core/configuration/Config"; import { GameType } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { MultiTabDetector } from "../../MultiTabDetector"; import { translateText } from "../../Utils"; -import { Controller } from "./Controller"; @customElement("multi-tab-modal") export class MultiTabModal extends LitElement implements Controller { diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 3686e499c0..2f0e4134c1 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -6,6 +6,7 @@ import { USER_SETTINGS_CHANGED_EVENT, UserSettings, } from "../../../core/game/UserSettings"; +import { Controller } from "../../Controller"; import { TickMetricsEvent, TogglePerformanceOverlayEvent, @@ -13,7 +14,6 @@ import { import type { LangSelector } from "../../LangSelector"; import { translateText } from "../../Utils"; import { FrameProfiler } from "../FrameProfiler"; -import { Controller } from "./Controller"; @customElement("performance-overlay") export class PerformanceOverlay extends LitElement implements Controller { diff --git a/src/client/graphics/layers/PlayerActionHandler.ts b/src/client/graphics/layers/PlayerActionHandler.ts index 6e876579cb..085e4d1ce1 100644 --- a/src/client/graphics/layers/PlayerActionHandler.ts +++ b/src/client/graphics/layers/PlayerActionHandler.ts @@ -15,7 +15,7 @@ import { SendSpawnIntentEvent, SendTargetPlayerIntentEvent, } from "../../Transport"; -import { UIState } from "../UIState"; +import { UIState } from "../../UIState"; export class PlayerActionHandler { constructor( diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts index 7c07d8d616..fc2d1e1c50 100644 --- a/src/client/graphics/layers/PlayerInfoOverlay.ts +++ b/src/client/graphics/layers/PlayerInfoOverlay.ts @@ -12,11 +12,13 @@ import { import { TileRef } from "../../../core/game/GameMap"; import { AllianceView } from "../../../core/game/GameUpdates"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { ContextMenuEvent, MouseMoveEvent, TouchEvent, } from "../../InputHandler"; +import { TransformHandler } from "../../TransformHandler"; import { getTranslatedPlayerTeamLabel, renderDuration, @@ -30,8 +32,6 @@ import { getPlayerIcons, IMAGE_ICON_KIND, } from "../PlayerIcons"; -import { TransformHandler } from "../TransformHandler"; -import { Controller } from "./Controller"; import { ImmunityBarVisibleEvent } from "./ImmunityTimer"; import { CloseRadialMenuEvent } from "./RadialMenu"; import "./RelationSmiley"; diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index 0a01dd95b0..f872f81405 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -15,6 +15,7 @@ import { GameView, PlayerView } from "../../../core/game/GameView"; import { Emoji, flattenedEmojiTable } from "../../../core/Util"; import { actionButton } from "../../components/ui/ActionButton"; import "../../components/ui/Divider"; +import { Controller } from "../../Controller"; import { CloseViewEvent, MouseUpEvent, @@ -28,15 +29,14 @@ import { SendEmojiIntentEvent, SendTargetPlayerIntentEvent, } from "../../Transport"; +import { UIState } from "../../UIState"; import { renderDuration, renderNumber, renderTroops, translateText, } from "../../Utils"; -import { UIState } from "../UIState"; import { ChatModal } from "./ChatModal"; -import { Controller } from "./Controller"; import { EmojiTable } from "./EmojiTable"; import "./PlayerModerationModal"; import "./SendResourceModal"; diff --git a/src/client/graphics/layers/RadialMenu.ts b/src/client/graphics/layers/RadialMenu.ts index fa6a31015a..740a0b90e4 100644 --- a/src/client/graphics/layers/RadialMenu.ts +++ b/src/client/graphics/layers/RadialMenu.ts @@ -1,10 +1,10 @@ import * as d3 from "d3"; import { assetUrl } from "../../../core/AssetUrls"; import { EventBus, GameEvent } from "../../../core/EventBus"; +import { Controller } from "../../Controller"; import { CloseViewEvent } from "../../InputHandler"; import { PlaySoundEffectEvent } from "../../sound/Sounds"; import { getSvgAspectRatio, translateText } from "../../Utils"; -import { Controller } from "./Controller"; import { CenterButtonElement, MenuElement, diff --git a/src/client/graphics/layers/RadialMenuElements.ts b/src/client/graphics/layers/RadialMenuElements.ts index 14e87c5e55..b29ea6791c 100644 --- a/src/client/graphics/layers/RadialMenuElements.ts +++ b/src/client/graphics/layers/RadialMenuElements.ts @@ -11,8 +11,8 @@ import { import { TileRef } from "../../../core/game/GameMap"; import { GameView, PlayerView } from "../../../core/game/GameView"; import { Emoji, findClosestBy, flattenedEmojiTable } from "../../../core/Util"; +import { UIState } from "../../UIState"; import { renderNumber, translateText } from "../../Utils"; -import { UIState } from "../UIState"; import { BuildItemDisplay, BuildMenu, flattenedBuildTable } from "./BuildMenu"; import { ChatIntegration } from "./ChatIntegration"; import { EmojiTable } from "./EmojiTable"; diff --git a/src/client/graphics/layers/ReplayPanel.ts b/src/client/graphics/layers/ReplayPanel.ts index 79e7b82c50..390e570923 100644 --- a/src/client/graphics/layers/ReplayPanel.ts +++ b/src/client/graphics/layers/ReplayPanel.ts @@ -2,13 +2,13 @@ import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { EventBus } from "../../../core/EventBus"; import { GameView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { ReplaySpeedChangeEvent } from "../../InputHandler"; import { defaultReplaySpeedMultiplier, ReplaySpeedMultiplier, } from "../../utilities/ReplaySpeedMultiplier"; import { translateText } from "../../Utils"; -import { Controller } from "./Controller"; export class ShowReplayPanelEvent { constructor( diff --git a/src/client/graphics/layers/SendResourceModal.ts b/src/client/graphics/layers/SendResourceModal.ts index 38495a6a17..0ce7db8d19 100644 --- a/src/client/graphics/layers/SendResourceModal.ts +++ b/src/client/graphics/layers/SendResourceModal.ts @@ -7,8 +7,8 @@ import { SendDonateGoldIntentEvent, SendDonateTroopsIntentEvent, } from "../../Transport"; +import { UIState } from "../../UIState"; import { renderTroops, translateText } from "../../Utils"; -import { UIState } from "../UIState"; @customElement("send-resource-modal") export class SendResourceModal extends LitElement { diff --git a/src/client/graphics/layers/SettingsModal.ts b/src/client/graphics/layers/SettingsModal.ts index 22a6cd7c5b..e1d19199d0 100644 --- a/src/client/graphics/layers/SettingsModal.ts +++ b/src/client/graphics/layers/SettingsModal.ts @@ -5,13 +5,13 @@ import { PauseGameIntentEvent } from "src/client/Transport"; import { assetUrl } from "../../../core/AssetUrls"; import { EventBus } from "../../../core/EventBus"; import { UserSettings } from "../../../core/game/UserSettings"; +import { Controller } from "../../Controller"; import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler"; import { translateText } from "../../Utils"; import { SetBackgroundMusicVolumeEvent, SetSoundEffectsVolumeEvent, } from "../../sound/Sounds"; -import { Controller } from "./Controller"; const structureIcon = assetUrl("images/CityIconWhite.svg"); const cursorPriceIcon = assetUrl("images/CursorPriceIconWhite.svg"); const darkModeIcon = assetUrl("images/DarkModeIconWhite.svg"); diff --git a/src/client/graphics/layers/SpawnTimer.ts b/src/client/graphics/layers/SpawnTimer.ts index e4e0250525..f4399362d9 100644 --- a/src/client/graphics/layers/SpawnTimer.ts +++ b/src/client/graphics/layers/SpawnTimer.ts @@ -3,8 +3,8 @@ import { customElement } from "lit/decorators.js"; import { EventBus, GameEvent } from "../../../core/EventBus"; import { GameMode, GameType, Team } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; -import { TransformHandler } from "../TransformHandler"; -import { Controller } from "./Controller"; +import { Controller } from "../../Controller"; +import { TransformHandler } from "../../TransformHandler"; export class SpawnBarVisibleEvent implements GameEvent { constructor(public readonly visible: boolean) {} diff --git a/src/client/graphics/layers/StructureDrawingUtils.ts b/src/client/graphics/layers/StructureDrawingUtils.ts index bffd1f4d24..6ea5ad33aa 100644 --- a/src/client/graphics/layers/StructureDrawingUtils.ts +++ b/src/client/graphics/layers/StructureDrawingUtils.ts @@ -7,7 +7,7 @@ import { UnitType, } from "../../../core/game/Game"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; -import { TransformHandler } from "../TransformHandler"; +import { TransformHandler } from "../../TransformHandler"; const anchorIcon = assetUrl("images/AnchorIcon.v1.png"); const cityIcon = assetUrl("images/CityIcon.v1.png"); const factoryIcon = assetUrl("images/FactoryUnit.v1.png"); diff --git a/src/client/graphics/layers/TeamStats.ts b/src/client/graphics/layers/TeamStats.ts index 6ce2c50f54..3df5500938 100644 --- a/src/client/graphics/layers/TeamStats.ts +++ b/src/client/graphics/layers/TeamStats.ts @@ -3,13 +3,13 @@ import { customElement, property } from "lit/decorators.js"; import { EventBus } from "../../../core/EventBus"; import { GameMode, Team, UnitType } from "../../../core/game/Game"; import { GameView, PlayerView } from "../../../core/game/GameView"; +import { Controller } from "../../Controller"; import { formatPercentage, renderNumber, renderTroops, translateText, } from "../../Utils"; -import { Controller } from "./Controller"; interface TeamEntry { teamName: string; diff --git a/src/client/graphics/layers/UnitDisplay.ts b/src/client/graphics/layers/UnitDisplay.ts index 456f16530f..8714d51b65 100644 --- a/src/client/graphics/layers/UnitDisplay.ts +++ b/src/client/graphics/layers/UnitDisplay.ts @@ -11,13 +11,10 @@ import { } from "../../../core/game/Game"; import { GameView } from "../../../core/game/GameView"; import { UserSettings } from "../../../core/game/UserSettings"; -import { - GhostStructureChangedEvent, - ToggleStructureEvent, -} from "../../InputHandler"; +import { Controller } from "../../Controller"; +import { ToggleStructureEvent } from "../../InputHandler"; +import { UIState } from "../../UIState"; import { renderNumber, translateText } from "../../Utils"; -import { UIState } from "../UIState"; -import { Controller } from "./Controller"; const warshipIcon = assetUrl("images/BattleshipIconWhite.svg"); const cityIcon = assetUrl("images/CityIconWhite.svg"); const factoryIcon = assetUrl("images/FactoryIconWhite.svg"); @@ -268,10 +265,8 @@ export class UnitDisplay extends LitElement implements Controller { @click=${() => { if (selected) { this.uiState.ghostStructure = null; - this.eventBus?.emit(new GhostStructureChangedEvent(null)); } else if (this.canBuild(unitType)) { this.uiState.ghostStructure = unitType; - this.eventBus?.emit(new GhostStructureChangedEvent(unitType)); } this.requestUpdate(); }} diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index 7907ab3173..7c7b8fdd66 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -12,6 +12,7 @@ import { GameUpdateType } from "../../../core/game/GameUpdates"; import { GameView } from "../../../core/game/GameView"; import { getUserMe } from "../../Api"; import "../../components/CosmeticButton"; +import { Controller } from "../../Controller"; import { fetchCosmetics, purchaseCosmetic, @@ -20,7 +21,6 @@ import { import { crazyGamesSDK } from "../../CrazyGamesSDK"; import { Platform } from "../../Platform"; import { SendWinnerEvent } from "../../Transport"; -import { Controller } from "./Controller"; @customElement("win-modal") export class WinModal extends LitElement implements Controller { diff --git a/src/client/graphics/ui/TextIndicator.ts b/src/client/graphics/ui/TextIndicator.ts index 1c49006a1d..1d68f7b0c6 100644 --- a/src/client/graphics/ui/TextIndicator.ts +++ b/src/client/graphics/ui/TextIndicator.ts @@ -1,5 +1,5 @@ import { Cell } from "src/core/game/Game"; -import { TransformHandler } from "../TransformHandler"; +import { TransformHandler } from "../../TransformHandler"; import { UIElement } from "./UIElement"; const MIN_TEXT_ZOOM = 1.1; diff --git a/tests/InputHandler.test.ts b/tests/InputHandler.test.ts index 3da402e6f2..f0a8f2e047 100644 --- a/tests/InputHandler.test.ts +++ b/tests/InputHandler.test.ts @@ -6,7 +6,7 @@ import { WarshipSelectionBoxCompleteEvent, WarshipSelectionBoxUpdateEvent, } from "../src/client/InputHandler"; -import { UIState } from "../src/client/graphics/UIState"; +import { UIState } from "../src/client/UIState"; import { EventBus } from "../src/core/EventBus"; import { UnitType } from "../src/core/game/Game"; import { GameView, PlayerView } from "../src/core/game/GameView"; @@ -928,13 +928,10 @@ describe("Warship box selection (Shift+drag)", () => { test("Shift keydown discards active ghostStructure", () => { uiState.ghostStructure = UnitType.Warship; - const emitSpy = vi.spyOn(eventBus, "emit"); window.dispatchEvent(new KeyboardEvent("keydown", { code: "ShiftLeft" })); expect(uiState.ghostStructure).toBeNull(); - const types = emitSpy.mock.calls.map((c) => c[0].constructor.name); - expect(types).toContain("GhostStructureChangedEvent"); }); test("Shift+drag emits WarshipSelectionBoxUpdateEvent", () => { From a743a31897ce49d46a37e4eba5c2306966fa525a Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 12:31:57 -0700 Subject: [PATCH 21/34] =?UTF-8?q?delete=20dead=20canvas2D=20utilities,=20r?= =?UTF-8?q?ename=20mountWebGLDebugRenderer=20=E2=86=92=20mountWebGLFrameLo?= =?UTF-8?q?op?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ProgressBar and StructureDrawingUtils had no production callers — only their own test referenced ProgressBar, and StructureDrawingUtils was a canvas2D-era helper module that nothing imports anymore. mountWebGLDebugRenderer was named back when WebGL was a side-by-side debug overlay; it's the only renderer now, so the "Debug" prefix is misleading. Also dropped the `\` keybind that hid the GL canvas — with no other renderer, hiding it just blanks the game. --- src/client/ClientGameRunner.ts | 13 +- src/client/graphics/ProgressBar.ts | 61 -- .../graphics/layers/StructureDrawingUtils.ts | 549 ------------------ tests/client/graphics/ProgressBar.test.ts | 55 -- 4 files changed, 3 insertions(+), 675 deletions(-) delete mode 100644 src/client/graphics/ProgressBar.ts delete mode 100644 src/client/graphics/layers/StructureDrawingUtils.ts delete mode 100644 tests/client/graphics/ProgressBar.test.ts diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 505abf8617..26b61103e4 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -294,7 +294,7 @@ function createWebGLView(terrainMap: TerrainMapData): { return { view, glCanvas, cachedWebGLFrameCallback }; } -function mountWebGLDebugRenderer( +function mountWebGLFrameLoop( terrainMap: TerrainMapData, view: WebGLGameView, glCanvas: HTMLCanvasElement, @@ -307,13 +307,6 @@ function mountWebGLDebugRenderer( const mapWidth = gameMap.width(); const mapHeight = gameMap.height(); - window.addEventListener("keydown", (e) => { - if (e.key === "\\") { - glCanvas.style.display = - glCanvas.style.display === "none" ? "block" : "none"; - } - }); - // Cache canvas dimensions to avoid forced reflows every frame. Reading // clientWidth/clientHeight flushes pending layout — at 60fps that's a // measurable cost. Only update on resize events from the observer. @@ -420,7 +413,7 @@ async function createClientGame( // Transparent fullscreen overlay used purely as the pointer-event / // bounding-rect target for InputHandler + TransformHandler. The actual - // map drawing happens on the WebGL canvas created in mountWebGLDebugRenderer. + // map drawing happens on the WebGL canvas created in createWebGLView. const inputOverlay = document.createElement("div"); inputOverlay.id = "game-input-overlay"; inputOverlay.style.position = "fixed"; @@ -444,7 +437,7 @@ async function createClientGame( view, ); - const { builder: webglBuilder } = mountWebGLDebugRenderer( + const { builder: webglBuilder } = mountWebGLFrameLoop( gameMap, view, glCanvas, diff --git a/src/client/graphics/ProgressBar.ts b/src/client/graphics/ProgressBar.ts deleted file mode 100644 index 52500bb873..0000000000 --- a/src/client/graphics/ProgressBar.ts +++ /dev/null @@ -1,61 +0,0 @@ -export class ProgressBar { - private static readonly CLEAR_PADDING = 2; - constructor( - private colors: string[] = [], - private ctx: CanvasRenderingContext2D, - private x: number, - private y: number, - private w: number, - private h: number, - private progress: number = 0, // Progress from 0 to 1 - ) { - this.setProgress(progress); - } - - setProgress(progress: number): void { - progress = Math.max(0, Math.min(1, progress)); - this.clear(); - // Draw the loading bar background - this.ctx.fillStyle = "rgba(0, 0, 0, 1)"; - this.ctx.fillRect(this.x - 1, this.y - 1, this.w, this.h); - - // Draw the loading progress - if (this.colors.length === 0) { - this.ctx.fillStyle = "#808080"; // default gray - } else { - const idx = Math.min( - this.colors.length - 1, - Math.floor(progress * this.colors.length), - ); - this.ctx.fillStyle = this.colors[idx]; - } - this.ctx.fillRect( - this.x, - this.y, - Math.max(1, Math.floor(progress * (this.w - 2))), - this.h - 2, - ); - this.progress = progress; - } - - clear() { - this.ctx.clearRect( - this.x - ProgressBar.CLEAR_PADDING, - this.y - ProgressBar.CLEAR_PADDING, - this.w + ProgressBar.CLEAR_PADDING, - this.h + ProgressBar.CLEAR_PADDING, - ); - } - - getX(): number { - return this.x; - } - - getY(): number { - return this.y; - } - - getProgress(): number { - return this.progress; - } -} diff --git a/src/client/graphics/layers/StructureDrawingUtils.ts b/src/client/graphics/layers/StructureDrawingUtils.ts deleted file mode 100644 index 6ea5ad33aa..0000000000 --- a/src/client/graphics/layers/StructureDrawingUtils.ts +++ /dev/null @@ -1,549 +0,0 @@ -import * as PIXI from "pixi.js"; -import { Theme } from "src/core/configuration/Theme"; -import { assetUrl } from "../../../core/AssetUrls"; -import { - Cell, - PlayerBuildableUnitType, - UnitType, -} from "../../../core/game/Game"; -import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; -import { TransformHandler } from "../../TransformHandler"; -const anchorIcon = assetUrl("images/AnchorIcon.v1.png"); -const cityIcon = assetUrl("images/CityIcon.v1.png"); -const factoryIcon = assetUrl("images/FactoryUnit.v1.png"); -const missileSiloIcon = assetUrl("images/MissileSiloUnit.v1.png"); -const SAMMissileIcon = assetUrl("images/SamLauncherUnit.v1.png"); -const shieldIcon = assetUrl("images/ShieldIcon.v1.png"); - -export const STRUCTURE_SHAPES: Partial> = { - [UnitType.City]: "circle", - [UnitType.Port]: "pentagon", - [UnitType.Factory]: "circle", - [UnitType.DefensePost]: "octagon", - [UnitType.SAMLauncher]: "square", - [UnitType.MissileSilo]: "triangle", - [UnitType.Warship]: "cross", - [UnitType.AtomBomb]: "cross", - [UnitType.HydrogenBomb]: "cross", - [UnitType.MIRV]: "cross", -}; -export const LEVEL_SCALE_FACTOR = 3; -export const ICON_SCALE_FACTOR_ZOOMED_IN = 3.5; -export const ICON_SCALE_FACTOR_ZOOMED_OUT = 1.4; -export const DOTS_ZOOM_THRESHOLD = 0.5; -export const ZOOM_THRESHOLD = 4.3; -export const ICON_SIZE = { - circle: 28, - octagon: 28, - pentagon: 30, - square: 28, - triangle: 28, - cross: 20, -}; -export const OFFSET_ZOOM_Y = 4; - -export type ShapeType = - | "triangle" - | "square" - | "pentagon" - | "octagon" - | "circle" - | "cross"; - -export class SpriteFactory { - private theme: Theme; - private game: GameView; - private transformHandler: TransformHandler; - private renderSprites: boolean; - private readonly textureCache: Map = new Map(); - private colorCanvas: HTMLCanvasElement | null = null; - private colorCtx: CanvasRenderingContext2D | null = null; - - private readonly structuresInfos: Map< - UnitType, - { iconPath: string; image: HTMLImageElement | null } - > = new Map([ - [UnitType.City, { iconPath: cityIcon, image: null }], - [UnitType.Factory, { iconPath: factoryIcon, image: null }], - [UnitType.DefensePost, { iconPath: shieldIcon, image: null }], - [UnitType.Port, { iconPath: anchorIcon, image: null }], - [UnitType.MissileSilo, { iconPath: missileSiloIcon, image: null }], - [UnitType.SAMLauncher, { iconPath: SAMMissileIcon, image: null }], - ]); - constructor( - theme: Theme, - game: GameView, - transformHandler: TransformHandler, - renderSprites: boolean, - ) { - this.theme = theme; - this.game = game; - this.transformHandler = transformHandler; - this.renderSprites = renderSprites; - this.structuresInfos.forEach((u, unitType) => this.loadIcon(u, unitType)); - } - - public clearCache() { - for (const texture of this.textureCache.values()) { - if (texture && texture !== PIXI.Texture.EMPTY) { - try { - texture.destroy(true); - } catch (e) { - console.error("Error clearing texture cache:", e); - } - } - } - this.textureCache.clear(); - this.colorCanvas = null; - this.colorCtx = null; - } - - private loadIcon( - unitInfo: { - iconPath: string; - image: HTMLImageElement | null; - }, - unitType: UnitType, - ) { - const image = new Image(); - // crossOrigin must be set before src so the fetch is CORS-checked. - // Without this, an icon served from CDN_BASE taints structureCanvas - // and PIXI.Texture.from rejects the upload to WebGL. - image.crossOrigin = "anonymous"; - image.src = unitInfo.iconPath; - image.onload = () => { - unitInfo.image = image; - this.invalidateTextureCache(unitType); - }; - image.onerror = () => { - console.error( - `Failed to load icon for ${unitType}: ${unitInfo.iconPath}`, - ); - }; - } - - private invalidateTextureCache(unitType: UnitType) { - for (const key of Array.from(this.textureCache.keys())) { - if (key.includes(`-${unitType}`)) { - const tex = this.textureCache.get(key); - if (tex && tex !== PIXI.Texture.EMPTY) { - tex.destroy(true); - } - this.textureCache.delete(key); - } - } - } - - createGhostContainer( - player: PlayerView, - ghostStage: PIXI.Container, - pos: { x: number; y: number }, - structureType: PlayerBuildableUnitType, - ): { - container: PIXI.Container; - priceText: PIXI.BitmapText; - priceBg: PIXI.Graphics; - priceGroup: PIXI.Container; - priceBox: { height: number; y: number; paddingX: number; minWidth: number }; - } { - const parentContainer = new PIXI.Container(); - const texture = this.createTexture( - structureType, - player, - false, - false, - true, - ); - const sprite = new PIXI.Sprite(texture); - sprite.anchor.set(0.5); - sprite.alpha = 0.5; - parentContainer.addChild(sprite); - - const priceText = new PIXI.BitmapText({ - text: "125K", - style: { fontFamily: "round_6x6_modified", fontSize: 12 }, - }); - priceText.anchor.set(0.5); - const priceGroup = new PIXI.Container(); - const boxHeight = 18; - const boxY = - (sprite.height > 0 ? sprite.height / 2 : 16) + boxHeight / 2 + 4; - - // a way to resize the pill horizontally based on the text width - const paddingX = 8; - const minWidth = 32; - const textWidth = priceText.width; - const boxWidth = Math.max(minWidth, textWidth + paddingX * 2); - - const priceBg = new PIXI.Graphics(); - priceBg - .roundRect(-boxWidth / 2, boxY - boxHeight / 2, boxWidth, boxHeight, 4) - .fill({ color: 0x000000, alpha: 0.65 }); - - priceText.position.set(0, boxY); - - priceGroup.addChild(priceBg); - priceGroup.addChild(priceText); - parentContainer.addChild(priceGroup); - - parentContainer.position.set(pos.x, pos.y); - parentContainer.scale.set( - Math.min(1, this.transformHandler.scale / ICON_SCALE_FACTOR_ZOOMED_OUT), - ); - ghostStage.addChild(parentContainer); - return { - container: parentContainer, - priceText, - priceBg, - priceGroup, - priceBox: { height: boxHeight, y: boxY, paddingX, minWidth }, - }; - } - - // --- internal helpers --- - - public createUnitContainer( - unit: UnitView, - options: { type?: "icon" | "dot" | "level"; stage: PIXI.Container }, - ): PIXI.Container { - const parentContainer = new PIXI.Container(); - const tile = unit.tile(); - const worldPos = new Cell(this.game.x(tile), this.game.y(tile)); - const screenPos = this.transformHandler.worldToCanvasCoordinates(worldPos); - - const isMarkedForDeletion = unit.markedForDeletion() !== false; - const isConstruction = unit.isUnderConstruction(); - const structureType = unit.type(); - const { type, stage } = options; - const { scale } = this.transformHandler; - - this.renderSprites = - this.game.config().userSettings()?.structureSprites() ?? true; - - if (type === "icon" || type === "dot") { - const texture = this.createTexture( - structureType, - unit.owner(), - isConstruction, - isMarkedForDeletion, - type === "icon", - ); - const sprite = new PIXI.Sprite(texture); - sprite.anchor.set(0.5); - parentContainer.addChild(sprite); - } - - if ((type === "icon" || type === "level") && unit.level() > 1) { - const text = new PIXI.BitmapText({ - text: unit.level().toString(), - style: { fontFamily: "round_6x6_modified", fontSize: 14 }, - }); - text.anchor.set(0.5); - - const shape = STRUCTURE_SHAPES[structureType]; - if (shape !== undefined) { - text.position.y = Math.round(-ICON_SIZE[shape] / 2 - 2); - } - parentContainer.addChild(text); - } - - const posX = Math.round(screenPos.x); - let posY = Math.round(screenPos.y); - if (type === "level" && scale >= ZOOM_THRESHOLD && this.renderSprites) { - posY = Math.round(screenPos.y - scale * OFFSET_ZOOM_Y); - } - parentContainer.position.set(posX, posY); - - if (type === "icon") { - const s = - scale >= ZOOM_THRESHOLD && !this.renderSprites - ? Math.max(1, scale / ICON_SCALE_FACTOR_ZOOMED_IN) - : Math.min(1, scale / ICON_SCALE_FACTOR_ZOOMED_OUT); - parentContainer.scale.set(s); - } else if (type === "level") { - parentContainer.scale.set(Math.max(1, scale / LEVEL_SCALE_FACTOR)); - } - - stage.addChild(parentContainer); - return parentContainer; - } - - private createTexture( - type: UnitType, - owner: PlayerView, - isConstruction: boolean, - isMarkedForDeletion: boolean, - renderIcon: boolean, - ): PIXI.Texture { - const cacheKeyBase = isConstruction - ? `construction-${type}` - : `${this.theme.territoryColor(owner).toRgbString()}-${type}`; - const cacheKey = - cacheKeyBase + - (renderIcon ? "-icon" : "") + - (isMarkedForDeletion ? "-deleted" : ""); - - if (this.textureCache.has(cacheKey)) { - return this.textureCache.get(cacheKey)!; - } - const shape = STRUCTURE_SHAPES[type]; - const texture = shape - ? this.createIcon( - owner, - type, - isConstruction, - isMarkedForDeletion, - shape, - renderIcon, - ) - : PIXI.Texture.EMPTY; - this.textureCache.set(cacheKey, texture); - return texture; - } - - private createIcon( - owner: PlayerView, - structureType: UnitType, - isConstruction: boolean, - isMarkedForDeletion: boolean, - shape: keyof typeof ICON_SIZE, - renderIcon: boolean, - ): PIXI.Texture { - const structureCanvas = document.createElement("canvas"); - let iconSize = ICON_SIZE[shape]; - if (!renderIcon) { - iconSize /= 2.5; - } - structureCanvas.width = Math.ceil(iconSize); - structureCanvas.height = Math.ceil(iconSize); - const context = structureCanvas.getContext("2d")!; - - // Use structureColors defined from the PlayerView. - context.fillStyle = isConstruction - ? "rgb(198,198,198)" - : owner.structureColors().light.toRgbString(); - context.strokeStyle = isConstruction - ? "rgb(127,127, 127)" - : owner.structureColors().dark.toRgbString(); - context.lineWidth = 1; - const halfIconSize = iconSize / 2; - - switch (shape) { - case "triangle": - context.beginPath(); - context.moveTo(halfIconSize, 1); // Top - context.lineTo(iconSize - 1, iconSize - 1); // Bottom right - context.lineTo(0, iconSize - 1); // Bottom left - context.closePath(); - context.fill(); - context.stroke(); - break; - - case "square": - context.fillRect(1, 1, iconSize - 2, iconSize - 2); - context.strokeRect(1, 1, iconSize - 3, iconSize - 3); - break; - - case "octagon": - { - const cx = halfIconSize; - const cy = halfIconSize; - const r = halfIconSize - 1; - const step = (Math.PI * 2) / 8; - - context.beginPath(); - for (let i = 0; i < 8; i++) { - const angle = step * i - Math.PI / 8; // slight rotation for flat top - const x = cx + r * Math.cos(angle); - const y = cy + r * Math.sin(angle); - if (i === 0) { - context.moveTo(x, y); - } else { - context.lineTo(x, y); - } - } - context.closePath(); - context.fill(); - context.stroke(); - } - break; - case "pentagon": - { - const cx = halfIconSize; - const cy = halfIconSize; - const r = halfIconSize - 1; - const step = (Math.PI * 2) / 5; - - context.beginPath(); - for (let i = 0; i < 5; i++) { - const angle = step * i - Math.PI / 2; // rotate to have flat base or point up - const x = cx + r * Math.cos(angle); - const y = cy + r * Math.sin(angle); - if (i === 0) { - context.moveTo(x, y); - } else { - context.lineTo(x, y); - } - } - context.closePath(); - context.fill(); - context.stroke(); - } - break; - case "cross": { - context.strokeStyle = "rgba(0, 0, 0, 1)"; - context.fillStyle = "rgba(0, 0, 0, 1)"; - - const gap = iconSize * 0.18; // gap at center - const lineLen = iconSize / 2; - context.save(); - context.translate(halfIconSize, halfIconSize); - // Up - context.beginPath(); - context.moveTo(0, -gap); - context.lineTo(0, -lineLen); - context.stroke(); - // Down - context.beginPath(); - context.moveTo(0, gap); - context.lineTo(0, lineLen); - context.stroke(); - // Left - context.beginPath(); - context.moveTo(-gap, 0); - context.lineTo(-lineLen, 0); - context.stroke(); - // Right - context.beginPath(); - context.moveTo(gap, 0); - context.lineTo(lineLen, 0); - context.stroke(); - context.restore(); - break; - } - - case "circle": - context.beginPath(); - context.arc( - halfIconSize, - halfIconSize, - halfIconSize - 1, - 0, - Math.PI * 2, - ); - context.fill(); - context.stroke(); - break; - - default: - throw new Error(`Unknown shape: ${shape}`); - } - - const structureInfo = this.structuresInfos.get(structureType); - - if (structureInfo?.image && renderIcon) { - const SHAPE_OFFSETS = { - triangle: [6, 11], - square: [5, 5], - octagon: [6, 6], - pentagon: [7, 7], - circle: [6, 6], - cross: [0, 0], - }; - const [offsetX, offsetY] = SHAPE_OFFSETS[shape] || [0, 0]; - context.drawImage( - this.getImageColored( - structureInfo.image, - owner.structureColors().dark.toRgbString(), - ), - offsetX, - offsetY, - ); - } - - if (isMarkedForDeletion) { - context.save(); - context.strokeStyle = "rgba(255, 64, 64, 0.95)"; - context.lineWidth = Math.max(2, Math.round(iconSize * 0.12)); - context.lineCap = "round"; - const padding = Math.max(2, iconSize * 0.12); - context.beginPath(); - context.moveTo(padding, padding); - context.lineTo(iconSize - padding, iconSize - padding); - context.moveTo(iconSize - padding, padding); - context.lineTo(padding, iconSize - padding); - context.stroke(); - context.restore(); - } - - return PIXI.Texture.from(structureCanvas, true); - } - - public createRange( - type: UnitType, - stage: PIXI.Container, - pos: { x: number; y: number }, - level?: number, - targetingAlly: boolean = false, - ): PIXI.Container | null { - if (stage === undefined) throw new Error("Not initialized"); - const parentContainer = new PIXI.Container(); - const circle = new PIXI.Graphics(); - let radius: number; - switch (type) { - case UnitType.SAMLauncher: - radius = this.game.config().samRange(level ?? 1); - break; - case UnitType.Factory: - radius = this.game.config().trainStationMaxRange(); - break; - case UnitType.DefensePost: - radius = this.game.config().defensePostRange(); - break; - case UnitType.AtomBomb: - radius = this.game.config().nukeMagnitudes(UnitType.AtomBomb).outer; - break; - case UnitType.HydrogenBomb: - radius = this.game.config().nukeMagnitudes(UnitType.HydrogenBomb).outer; - break; - default: - return null; - } - // Add warning colors (red/orange) when targeting an ally to indicate alliance will break - const isNuke = type === UnitType.AtomBomb || type === UnitType.HydrogenBomb; - const fillColor = targetingAlly && isNuke ? 0xff6b35 : 0xffffff; - const fillAlpha = targetingAlly && isNuke ? 0.35 : 0.2; - const strokeColor = targetingAlly && isNuke ? 0xff4444 : 0xffffff; - const strokeAlpha = targetingAlly && isNuke ? 0.8 : 0.5; - const strokeWidth = targetingAlly && isNuke ? 2 : 1; - - circle - .circle(0, 0, radius) - .fill({ color: fillColor, alpha: fillAlpha }) - .stroke({ width: strokeWidth, color: strokeColor, alpha: strokeAlpha }); - parentContainer.addChild(circle); - parentContainer.position.set(pos.x, pos.y); - parentContainer.scale.set(this.transformHandler.scale); - stage.addChild(parentContainer); - return parentContainer; - } - - private getImageColored( - image: HTMLImageElement, - color: string, - ): HTMLCanvasElement { - if (!this.colorCanvas || !this.colorCtx) { - this.colorCanvas = document.createElement("canvas"); - this.colorCtx = this.colorCanvas.getContext("2d")!; - } - const { colorCanvas, colorCtx: ctx } = this; - if (colorCanvas.width !== image.width) colorCanvas.width = image.width; - if (colorCanvas.height !== image.height) colorCanvas.height = image.height; - ctx.globalCompositeOperation = "source-over"; - ctx.fillStyle = color; - ctx.fillRect(0, 0, colorCanvas.width, colorCanvas.height); - ctx.globalCompositeOperation = "destination-in"; - ctx.drawImage(image, 0, 0); - return colorCanvas; - } -} diff --git a/tests/client/graphics/ProgressBar.test.ts b/tests/client/graphics/ProgressBar.test.ts deleted file mode 100644 index 5fc845fbfb..0000000000 --- a/tests/client/graphics/ProgressBar.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { ProgressBar } from "../../../src/client/graphics/ProgressBar"; - -describe("ProgressBar", () => { - let ctx: CanvasRenderingContext2D; - let canvas: HTMLCanvasElement; - - beforeEach(() => { - canvas = document.createElement("canvas"); - canvas.width = 100; - canvas.height = 20; - ctx = canvas.getContext("2d")!; - }); - - it("should initialize and draw the background", () => { - const spyClearRect = vi.spyOn(ctx, "clearRect"); - const spyFillRect = vi.spyOn(ctx, "fillRect"); - const spyFillStyle = vi.spyOn(ctx, "fillStyle", "set"); - const bar = new ProgressBar(["#ff0000", "#00ff00"], ctx, 2, 2, 80, 10, 0.5); - expect(spyClearRect).toHaveBeenCalledWith(0, 0, 82, 12); - expect(spyFillRect).toHaveBeenCalledWith(1, 1, 80, 10); - expect(spyFillStyle).toHaveBeenCalledWith("#00ff00"); - expect(bar.getX()).toBe(2); - expect(bar.getY()).toBe(2); - }); - - it("should set progress and draw the progress bar", () => { - const bar = new ProgressBar(["#ff0000", "#00ff00"], ctx, 2, 2, 80, 10); - const spyFillRect = vi.spyOn(ctx, "fillRect"); - bar.setProgress(0.5); - expect(bar.getProgress()).toBe(0.5); - expect(spyFillRect).toHaveBeenCalledWith( - 2, - 2, - Math.floor(0.5 * (80 - 2)), - 8, - ); - expect(ctx.fillStyle).toBe("#00ff00"); - - bar.setProgress(0.1); - expect(ctx.fillStyle).toBe("#ff0000"); - }); - - it("should clamp progress between 0 and 1 on init", () => { - const bar = new ProgressBar(["#ff0000", "#00ff00"], ctx, 2, 2, 80, 10, -1); - expect(bar.getProgress()).toBe(0); - const bar2 = new ProgressBar(["#ff0000", "#00ff00"], ctx, 2, 2, 80, 10, 2); - expect(bar2.getProgress()).toBe(1); - }); - - it("should handle empty colors array gracefully", () => { - const bar = new ProgressBar([], ctx, 2, 2, 80, 10, 0.5); - expect(() => bar.setProgress(0.5)).not.toThrow(); - expect(ctx.fillStyle).toBe("#808080"); - }); -}); From 8a4b12c4d6045d470526c61746451b04bc080ce7 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 12:54:57 -0700 Subject: [PATCH 22/34] move WebGL atlases into resources/atlases/, route through assetUrl() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/client/render/gl/assets/ held 11 atlas files (PNGs + JSON metadata) that bypassed the asset-manifest pipeline — they were imported via Vite's ?url query, bundled, and served same-origin instead of going through the CDN like every other game asset. Moved them to resources/atlases/, switched the PNG imports to assetUrl("atlases/...") so they flow through the manifest, and updated the JSON metadata imports to "resources/atlases/..." paths. Also dropped an orphan copy of MissileSiloIconWhite.svg (no callers; resources/images/ already had the canonical version). render-settings.json stays in src/ — it's renderer tuning config consumed at bundle time, not a URL-served asset. --- .../atlases}/emoji-atlas-meta.json | 0 .../atlases}/emoji-atlas.png | Bin .../atlases}/flag-atlas-meta.json | 0 .../atlases}/flag-atlas.png | Bin .../atlases}/fx-atlas-meta.json | 0 .../assets => resources/atlases}/fx-atlas.png | Bin .../atlases}/icon-atlas.png | Bin .../atlases}/msdf-atlas.json | 0 .../atlases}/msdf-atlas.png | Bin .../atlases}/status-atlas-meta.json | 0 .../atlases}/status-atlas.png | Bin .../atlases}/unit-atlas.png | Bin .../render/gl/assets/MissileSiloIconWhite.svg | 72 ------------------ .../render/gl/passes/conquest-popup-pass.ts | 4 +- .../gl/passes/fx-pass/fx-sprite-pass.ts | 6 +- .../render/gl/passes/name-pass/atlas-data.ts | 6 +- .../gl/passes/name-pass/debug-program.ts | 2 +- .../gl/passes/name-pass/icon-program.ts | 10 ++- .../passes/name-pass/status-icon-program.ts | 6 +- .../gl/passes/name-pass/text-program.ts | 4 +- .../render/gl/passes/radial-menu-pass.ts | 6 +- .../render/gl/passes/structure-level-pass.ts | 4 +- src/client/render/gl/passes/structure-pass.ts | 4 +- src/client/render/gl/passes/unit-pass.ts | 4 +- 24 files changed, 35 insertions(+), 93 deletions(-) rename {src/client/render/gl/assets => resources/atlases}/emoji-atlas-meta.json (100%) rename {src/client/render/gl/assets => resources/atlases}/emoji-atlas.png (100%) rename {src/client/render/gl/assets => resources/atlases}/flag-atlas-meta.json (100%) rename {src/client/render/gl/assets => resources/atlases}/flag-atlas.png (100%) rename {src/client/render/gl/assets => resources/atlases}/fx-atlas-meta.json (100%) rename {src/client/render/gl/assets => resources/atlases}/fx-atlas.png (100%) rename {src/client/render/gl/assets => resources/atlases}/icon-atlas.png (100%) rename {src/client/render/gl/assets => resources/atlases}/msdf-atlas.json (100%) rename {src/client/render/gl/assets => resources/atlases}/msdf-atlas.png (100%) rename {src/client/render/gl/assets => resources/atlases}/status-atlas-meta.json (100%) rename {src/client/render/gl/assets => resources/atlases}/status-atlas.png (100%) rename {src/client/render/gl/assets => resources/atlases}/unit-atlas.png (100%) delete mode 100644 src/client/render/gl/assets/MissileSiloIconWhite.svg diff --git a/src/client/render/gl/assets/emoji-atlas-meta.json b/resources/atlases/emoji-atlas-meta.json similarity index 100% rename from src/client/render/gl/assets/emoji-atlas-meta.json rename to resources/atlases/emoji-atlas-meta.json diff --git a/src/client/render/gl/assets/emoji-atlas.png b/resources/atlases/emoji-atlas.png similarity index 100% rename from src/client/render/gl/assets/emoji-atlas.png rename to resources/atlases/emoji-atlas.png diff --git a/src/client/render/gl/assets/flag-atlas-meta.json b/resources/atlases/flag-atlas-meta.json similarity index 100% rename from src/client/render/gl/assets/flag-atlas-meta.json rename to resources/atlases/flag-atlas-meta.json diff --git a/src/client/render/gl/assets/flag-atlas.png b/resources/atlases/flag-atlas.png similarity index 100% rename from src/client/render/gl/assets/flag-atlas.png rename to resources/atlases/flag-atlas.png diff --git a/src/client/render/gl/assets/fx-atlas-meta.json b/resources/atlases/fx-atlas-meta.json similarity index 100% rename from src/client/render/gl/assets/fx-atlas-meta.json rename to resources/atlases/fx-atlas-meta.json diff --git a/src/client/render/gl/assets/fx-atlas.png b/resources/atlases/fx-atlas.png similarity index 100% rename from src/client/render/gl/assets/fx-atlas.png rename to resources/atlases/fx-atlas.png diff --git a/src/client/render/gl/assets/icon-atlas.png b/resources/atlases/icon-atlas.png similarity index 100% rename from src/client/render/gl/assets/icon-atlas.png rename to resources/atlases/icon-atlas.png diff --git a/src/client/render/gl/assets/msdf-atlas.json b/resources/atlases/msdf-atlas.json similarity index 100% rename from src/client/render/gl/assets/msdf-atlas.json rename to resources/atlases/msdf-atlas.json diff --git a/src/client/render/gl/assets/msdf-atlas.png b/resources/atlases/msdf-atlas.png similarity index 100% rename from src/client/render/gl/assets/msdf-atlas.png rename to resources/atlases/msdf-atlas.png diff --git a/src/client/render/gl/assets/status-atlas-meta.json b/resources/atlases/status-atlas-meta.json similarity index 100% rename from src/client/render/gl/assets/status-atlas-meta.json rename to resources/atlases/status-atlas-meta.json diff --git a/src/client/render/gl/assets/status-atlas.png b/resources/atlases/status-atlas.png similarity index 100% rename from src/client/render/gl/assets/status-atlas.png rename to resources/atlases/status-atlas.png diff --git a/src/client/render/gl/assets/unit-atlas.png b/resources/atlases/unit-atlas.png similarity index 100% rename from src/client/render/gl/assets/unit-atlas.png rename to resources/atlases/unit-atlas.png diff --git a/src/client/render/gl/assets/MissileSiloIconWhite.svg b/src/client/render/gl/assets/MissileSiloIconWhite.svg deleted file mode 100644 index 4235be74e7..0000000000 --- a/src/client/render/gl/assets/MissileSiloIconWhite.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/client/render/gl/passes/conquest-popup-pass.ts b/src/client/render/gl/passes/conquest-popup-pass.ts index 01cb68640c..f45ff4c3f5 100644 --- a/src/client/render/gl/passes/conquest-popup-pass.ts +++ b/src/client/render/gl/passes/conquest-popup-pass.ts @@ -15,10 +15,12 @@ import { buildGlyphMetricsTex } from "./name-pass/data-textures"; import { layoutString } from "./name-pass/text-layout"; import { CHAR_RANGE, MAX_CHARS } from "./name-pass/types"; -import atlasUrl from "../assets/msdf-atlas.png?url"; +import { assetUrl } from "src/core/AssetUrls"; import fragSrc from "../shaders/conquest-popup/conquest-popup.frag.glsl?raw"; import vertSrc from "../shaders/conquest-popup/conquest-popup.vert.glsl?raw"; +const atlasUrl = assetUrl("atlases/msdf-atlas.png"); + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- diff --git a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts b/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts index ecd61d0a68..aeebb1ea83 100644 --- a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts +++ b/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts @@ -18,12 +18,14 @@ import { DynamicInstanceBuffer } from "../../dynamic-buffer"; import type { RenderSettings } from "../../render-settings"; import { createProgram, shaderSrc } from "../../utils/gl-utils"; -import fxAtlasMeta from "../../assets/fx-atlas-meta.json"; -import fxAtlasUrl from "../../assets/fx-atlas.png?url"; +import fxAtlasMeta from "resources/atlases/fx-atlas-meta.json"; +import { assetUrl } from "src/core/AssetUrls"; import spriteFragSrc from "../../shaders/fx/sprite.frag.glsl?raw"; import spriteVertSrc from "../../shaders/fx/sprite.vert.glsl?raw"; +const fxAtlasUrl = assetUrl("atlases/fx-atlas.png"); + // --------------------------------------------------------------------------- // FX type indices (atlas row) // --------------------------------------------------------------------------- diff --git a/src/client/render/gl/passes/name-pass/atlas-data.ts b/src/client/render/gl/passes/name-pass/atlas-data.ts index 73ef508c36..65f49a2854 100644 --- a/src/client/render/gl/passes/name-pass/atlas-data.ts +++ b/src/client/render/gl/passes/name-pass/atlas-data.ts @@ -3,9 +3,9 @@ * kerning data, and icon atlas index maps from static JSON assets. */ -import emojiAtlasMeta from "../../assets/emoji-atlas-meta.json"; -import flagAtlasMeta from "../../assets/flag-atlas-meta.json"; -import atlasData from "../../assets/msdf-atlas.json"; +import emojiAtlasMeta from "resources/atlases/emoji-atlas-meta.json"; +import flagAtlasMeta from "resources/atlases/flag-atlas-meta.json"; +import atlasData from "resources/atlases/msdf-atlas.json"; import type { BMChar, BMKerning, ParsedAtlas } from "./types"; import { CHAR_RANGE } from "./types"; diff --git a/src/client/render/gl/passes/name-pass/debug-program.ts b/src/client/render/gl/passes/name-pass/debug-program.ts index 71eab14d68..fb9ae01d55 100644 --- a/src/client/render/gl/passes/name-pass/debug-program.ts +++ b/src/client/render/gl/passes/name-pass/debug-program.ts @@ -5,7 +5,7 @@ * The shared playerDataTex is passed in but not owned/deleted. */ -import flagAtlasMeta from "../../assets/flag-atlas-meta.json"; +import flagAtlasMeta from "resources/atlases/flag-atlas-meta.json"; import type { RenderSettings } from "../../render-settings"; import debugBoxFragSrc from "../../shaders/name/debug-box.frag.glsl?raw"; import debugBoxVertSrc from "../../shaders/name/debug-box.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/name-pass/icon-program.ts b/src/client/render/gl/passes/name-pass/icon-program.ts index fb04f63537..02825dc02f 100644 --- a/src/client/render/gl/passes/name-pass/icon-program.ts +++ b/src/client/render/gl/passes/name-pass/icon-program.ts @@ -5,16 +5,18 @@ * The shared playerDataTex is passed in but not owned/deleted. */ -import emojiAtlasMeta from "../../assets/emoji-atlas-meta.json"; -import emojiAtlasUrl from "../../assets/emoji-atlas.png?url"; -import flagAtlasMeta from "../../assets/flag-atlas-meta.json"; -import flagAtlasUrl from "../../assets/flag-atlas.png?url"; +import emojiAtlasMeta from "resources/atlases/emoji-atlas-meta.json"; +import flagAtlasMeta from "resources/atlases/flag-atlas-meta.json"; +import { assetUrl } from "src/core/AssetUrls"; import type { RenderSettings } from "../../render-settings"; import iconFragSrc from "../../shaders/name/icon.frag.glsl?raw"; import iconVertSrc from "../../shaders/name/icon.vert.glsl?raw"; import { createProgram } from "../../utils/gl-utils"; import type { ParsedAtlas } from "./types"; +const emojiAtlasUrl = assetUrl("atlases/emoji-atlas.png"); +const flagAtlasUrl = assetUrl("atlases/flag-atlas.png"); + export class IconProgram { private gl: WebGL2RenderingContext; private program: WebGLProgram; diff --git a/src/client/render/gl/passes/name-pass/status-icon-program.ts b/src/client/render/gl/passes/name-pass/status-icon-program.ts index 6f04ad5b68..07db99d6b8 100644 --- a/src/client/render/gl/passes/name-pass/status-icon-program.ts +++ b/src/client/render/gl/passes/name-pass/status-icon-program.ts @@ -9,14 +9,16 @@ * The shared playerDataTex is passed in but not owned/deleted. */ -import statusAtlasMeta from "../../assets/status-atlas-meta.json"; -import statusAtlasUrl from "../../assets/status-atlas.png?url"; +import statusAtlasMeta from "resources/atlases/status-atlas-meta.json"; +import { assetUrl } from "src/core/AssetUrls"; import type { RenderSettings } from "../../render-settings"; import statusFragSrc from "../../shaders/name/status-icon.frag.glsl?raw"; import statusVertSrc from "../../shaders/name/status-icon.vert.glsl?raw"; import { createProgram } from "../../utils/gl-utils"; import type { ParsedAtlas } from "./types"; +const statusAtlasUrl = assetUrl("atlases/status-atlas.png"); + const MAX_STATUS_ICONS = 8; export class StatusIconProgram { diff --git a/src/client/render/gl/passes/name-pass/text-program.ts b/src/client/render/gl/passes/name-pass/text-program.ts index 8db2cadae8..8c38050908 100644 --- a/src/client/render/gl/passes/name-pass/text-program.ts +++ b/src/client/render/gl/passes/name-pass/text-program.ts @@ -6,7 +6,7 @@ * and bound at draw time but not owned/deleted by this class. */ -import atlasUrl from "../../assets/msdf-atlas.png?url"; +import { assetUrl } from "src/core/AssetUrls"; import type { RenderSettings } from "../../render-settings"; import nameFragSrc from "../../shaders/name/name.frag.glsl?raw"; import nameVertSrc from "../../shaders/name/name.vert.glsl?raw"; @@ -14,6 +14,8 @@ import { createProgram, shaderSrc } from "../../utils/gl-utils"; import type { ParsedAtlas } from "./types"; import { LINES_PER_PLAYER, MAX_CHARS } from "./types"; +const atlasUrl = assetUrl("atlases/msdf-atlas.png"); + export interface TextProgramTextures { glyphMetrics: WebGLTexture; cursor: WebGLTexture; diff --git a/src/client/render/gl/passes/radial-menu-pass.ts b/src/client/render/gl/passes/radial-menu-pass.ts index d195897b96..2b95965882 100644 --- a/src/client/render/gl/passes/radial-menu-pass.ts +++ b/src/client/render/gl/passes/radial-menu-pass.ts @@ -20,8 +20,10 @@ import arcVertSrc from "../shaders/radial-menu/arcs.vert.glsl?raw"; import iconFragSrc from "../shaders/radial-menu/icon.frag.glsl?raw"; import iconVertSrc from "../shaders/radial-menu/icon.vert.glsl?raw"; -import emojiAtlasMeta from "../assets/emoji-atlas-meta.json"; -import emojiAtlasUrl from "../assets/emoji-atlas.png?url"; +import emojiAtlasMeta from "resources/atlases/emoji-atlas-meta.json"; +import { assetUrl } from "src/core/AssetUrls"; + +const emojiAtlasUrl = assetUrl("atlases/emoji-atlas.png"); // --------------------------------------------------------------------------- // Ring layout configs (CSS pixels) diff --git a/src/client/render/gl/passes/structure-level-pass.ts b/src/client/render/gl/passes/structure-level-pass.ts index 27e616458c..b50cf29f8a 100644 --- a/src/client/render/gl/passes/structure-level-pass.ts +++ b/src/client/render/gl/passes/structure-level-pass.ts @@ -26,10 +26,12 @@ import { buildGlyphMetricsTex } from "./name-pass/data-textures"; import { layoutString } from "./name-pass/text-layout"; import { CHAR_RANGE, MAX_CHARS } from "./name-pass/types"; -import atlasUrl from "../assets/msdf-atlas.png?url"; +import { assetUrl } from "src/core/AssetUrls"; import fragSrc from "../shaders/structure-level/structure-level.frag.glsl?raw"; import vertSrc from "../shaders/structure-level/structure-level.vert.glsl?raw"; +const atlasUrl = assetUrl("atlases/msdf-atlas.png"); + // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- diff --git a/src/client/render/gl/passes/structure-pass.ts b/src/client/render/gl/passes/structure-pass.ts index d5874fad85..836bb7880b 100644 --- a/src/client/render/gl/passes/structure-pass.ts +++ b/src/client/render/gl/passes/structure-pass.ts @@ -28,11 +28,11 @@ import type { RenderSettings } from "../render-settings"; import { getPaletteSize } from "../utils/color-utils"; import { createProgram, shaderSrc } from "../utils/gl-utils"; +import { assetUrl } from "src/core/AssetUrls"; import structureFragSrc from "../shaders/structure/structure.frag.glsl?raw"; import structureVertSrc from "../shaders/structure/structure.vert.glsl?raw"; -// Pre-built icon atlas (generated by scripts/generate-sprite-atlases.mjs) -import iconAtlasUrl from "../assets/icon-atlas.png?url"; +const iconAtlasUrl = assetUrl("atlases/icon-atlas.png"); // --------------------------------------------------------------------------- // Constants diff --git a/src/client/render/gl/passes/unit-pass.ts b/src/client/render/gl/passes/unit-pass.ts index 08be21d472..2b88039846 100644 --- a/src/client/render/gl/passes/unit-pass.ts +++ b/src/client/render/gl/passes/unit-pass.ts @@ -32,6 +32,7 @@ * Shells emit 2 instances (pos + lastPos) to match live game's 2-pixel trail. */ +import { assetUrl } from "src/core/AssetUrls"; import type { RendererConfig, UnitState } from "../../types"; import { TrainType, @@ -53,8 +54,7 @@ import unitVertSrc from "../shaders/unit/unit.vert.glsl?raw"; import { getPaletteSize } from "../utils/color-utils"; import { createProgram, shaderSrc } from "../utils/gl-utils"; -// Pre-built sprite atlas (generated by scripts/generate-sprite-atlases.mjs) -import unitAtlasUrl from "../assets/unit-atlas.png?url"; +const unitAtlasUrl = assetUrl("atlases/unit-atlas.png"); // --------------------------------------------------------------------------- // Constants From be182bb7f71ad4fe46b5ccf635f8b2e3ca977e71 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 13:02:00 -0700 Subject: [PATCH 23/34] delete dead canvas2D FX system graphics/fx/ (6 files) and the AnimatedSprite/AnimatedSpriteLoader pair were the canvas2D-era visual-effects pipeline. WebGL has its own FX stack now (render/gl/passes/fx-pass/), so nothing outside the dead cluster imported any of these. The only "reference" left was a stale comment in fx-sprite-pass.ts. --- src/client/graphics/AnimatedSprite.ts | 86 ------- src/client/graphics/AnimatedSpriteLoader.ts | 236 -------------------- src/client/graphics/fx/ConquestFx.ts | 28 --- src/client/graphics/fx/Fx.ts | 19 -- src/client/graphics/fx/NukeFx.ts | 110 --------- src/client/graphics/fx/SpriteFx.ts | 97 -------- src/client/graphics/fx/Timeline.ts | 33 --- src/client/graphics/fx/UnitExplosionFx.ts | 47 ---- 8 files changed, 656 deletions(-) delete mode 100644 src/client/graphics/AnimatedSprite.ts delete mode 100644 src/client/graphics/AnimatedSpriteLoader.ts delete mode 100644 src/client/graphics/fx/ConquestFx.ts delete mode 100644 src/client/graphics/fx/Fx.ts delete mode 100644 src/client/graphics/fx/NukeFx.ts delete mode 100644 src/client/graphics/fx/SpriteFx.ts delete mode 100644 src/client/graphics/fx/Timeline.ts delete mode 100644 src/client/graphics/fx/UnitExplosionFx.ts diff --git a/src/client/graphics/AnimatedSprite.ts b/src/client/graphics/AnimatedSprite.ts deleted file mode 100644 index 66648951c9..0000000000 --- a/src/client/graphics/AnimatedSprite.ts +++ /dev/null @@ -1,86 +0,0 @@ -export class AnimatedSprite { - private frameHeight: number; - private frameWidth: number; - private currentFrame: number = 0; - private elapsedTime: number = 0; - private active: boolean = true; - - constructor( - private image: CanvasImageSource, - private frameCount: number, - private frameDuration: number, // in milliseconds - private looping: boolean = false, - private originX: number, - private originY: number, - ) { - if (frameCount <= 0) { - throw new Error("Animated sprite should at least have one frame"); - } - if ("height" in image && "width" in image) { - this.frameHeight = (image as HTMLImageElement | HTMLCanvasElement).height; - this.frameWidth = Math.floor( - (image as HTMLImageElement | HTMLCanvasElement).width / frameCount, - ); - } else { - throw new Error( - "Image source must have 'width' and 'height' properties.", - ); - } - } - - update(deltaTime: number) { - if (!this.active) return; - this.elapsedTime += deltaTime; - if (this.elapsedTime >= this.frameDuration) { - this.elapsedTime -= this.frameDuration; - this.currentFrame++; - - if (this.currentFrame >= this.frameCount) { - if (this.looping) { - this.currentFrame = 0; - } else { - this.currentFrame = this.frameCount - 1; - this.active = false; - } - } - } - } - - isActive(): boolean { - return this.active; - } - - lifeTime(): number | undefined { - if (this.looping) { - return undefined; - } - return this.frameDuration * this.frameCount; - } - - draw(ctx: CanvasRenderingContext2D, x: number, y: number) { - const drawX = x - this.originX; - const drawY = y - this.originY; - - ctx.drawImage( - this.image, - this.currentFrame * this.frameWidth, - 0, - this.frameWidth, - this.frameHeight, - drawX, - drawY, - this.frameWidth, - this.frameHeight, - ); - } - - reset() { - this.currentFrame = 0; - this.elapsedTime = 0; - } - - setOrigin(xRatio: number, yRatio: number) { - this.originX = xRatio; - this.originY = yRatio; - } -} diff --git a/src/client/graphics/AnimatedSpriteLoader.ts b/src/client/graphics/AnimatedSpriteLoader.ts deleted file mode 100644 index d32dfe87bb..0000000000 --- a/src/client/graphics/AnimatedSpriteLoader.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { Theme } from "src/core/configuration/Theme"; -import miniBigSmoke from "../../../resources/sprites/bigsmoke.png"; -import buildingExplosion from "../../../resources/sprites/buildingExplosion.png"; -import conquestSword from "../../../resources/sprites/conquestSword.png"; -import dust from "../../../resources/sprites/dust.png"; -import miniExplosion from "../../../resources/sprites/miniExplosion.png"; -import miniFire from "../../../resources/sprites/minifire.png"; -import nuke from "../../../resources/sprites/nukeExplosion.png"; -import SAMExplosion from "../../../resources/sprites/samExplosion.png"; -import sinkingShip from "../../../resources/sprites/sinkingShip.png"; -import miniSmoke from "../../../resources/sprites/smoke.png"; -import miniSmokeAndFire from "../../../resources/sprites/smokeAndFire.png"; -import unitExplosion from "../../../resources/sprites/unitExplosion.png"; -import { PlayerView } from "../../core/game/GameView"; -import { AnimatedSprite } from "./AnimatedSprite"; -import { FxType } from "./fx/Fx"; -import { colorizeCanvas } from "./SpriteLoader"; - -type AnimatedSpriteConfig = { - url: string; - frameCount: number; - frameDuration: number; // ms per frame - looping?: boolean; - originX: number; - originY: number; -}; - -const ANIMATED_SPRITE_CONFIG: Partial> = { - [FxType.MiniFire]: { - url: miniFire, - frameCount: 6, - frameDuration: 100, - looping: true, - originX: 3, - originY: 11, - }, - [FxType.MiniSmoke]: { - url: miniSmoke, - frameCount: 4, - frameDuration: 120, - looping: true, - originX: 2, - originY: 10, - }, - [FxType.MiniBigSmoke]: { - url: miniBigSmoke, - frameCount: 5, - frameDuration: 120, - looping: true, - originX: 9, - originY: 14, - }, - [FxType.MiniSmokeAndFire]: { - url: miniSmokeAndFire, - frameCount: 6, - frameDuration: 120, - looping: true, - originX: 9, - originY: 14, - }, - [FxType.MiniExplosion]: { - url: miniExplosion, - frameCount: 4, - frameDuration: 70, - looping: false, - originX: 6, - originY: 6, - }, - [FxType.Dust]: { - url: dust, - frameCount: 3, - frameDuration: 100, - looping: false, - originX: 4, - originY: 5, - }, - [FxType.UnitExplosion]: { - url: unitExplosion, - frameCount: 4, - frameDuration: 70, - looping: false, - originX: 9, - originY: 9, - }, - [FxType.BuildingExplosion]: { - url: buildingExplosion, - frameCount: 10, - frameDuration: 70, - looping: false, - originX: 8, - originY: 8, - }, - [FxType.SinkingShip]: { - url: sinkingShip, - frameCount: 14, - frameDuration: 90, - looping: false, - originX: 7, - originY: 7, - }, - [FxType.Nuke]: { - url: nuke, - frameCount: 9, - frameDuration: 70, - looping: false, - originX: 30, - originY: 30, - }, - [FxType.SAMExplosion]: { - url: SAMExplosion, - frameCount: 9, - frameDuration: 70, - looping: false, - originX: 23, - originY: 19, - }, - [FxType.Conquest]: { - url: conquestSword, - frameCount: 10, - frameDuration: 90, - looping: false, - originX: 10, - originY: 16, - }, -}; - -export class AnimatedSpriteLoader { - private animatedSpriteImageMap: Map = new Map(); - // Do not color the same sprite twice - private coloredAnimatedSpriteCache: Map = - new Map(); - - public async loadAllAnimatedSpriteImages(): Promise { - const entries = Object.entries(ANIMATED_SPRITE_CONFIG); - - await Promise.all( - entries.map(async ([fxType, config]) => { - const typedFxType = fxType as FxType; - if (!config?.url) return; - - try { - const img = new Image(); - img.crossOrigin = "anonymous"; - img.src = config.url; - - await new Promise((resolve, reject) => { - img.onload = () => resolve(); - img.onerror = (e) => reject(e); - }); - - const canvas = document.createElement("canvas"); - canvas.width = img.width; - canvas.height = img.height; - canvas.getContext("2d")!.drawImage(img, 0, 0); - - this.animatedSpriteImageMap.set(typedFxType, canvas); - } catch (err) { - console.error(`Failed to load sprite for ${typedFxType}:`, err); - } - }), - ); - } - - private createRegularAnimatedSprite(fxType: FxType): AnimatedSprite | null { - const config = ANIMATED_SPRITE_CONFIG[fxType]; - const image = this.animatedSpriteImageMap.get(fxType); - if (!config || !image) return null; - - return new AnimatedSprite( - image, - config.frameCount, - config.frameDuration, - config.looping ?? true, - config.originX, - config.originY, - ); - } - - private getColoredAnimatedSprite( - owner: PlayerView, - fxType: FxType, - theme: Theme, - ): HTMLCanvasElement | null { - const baseImage = this.animatedSpriteImageMap.get(fxType); - const config = ANIMATED_SPRITE_CONFIG[fxType]; - if (!baseImage || !config) return null; - const territoryColor = owner.territoryColor(); - const borderColor = owner.borderColor(); - const spawnHighlightColor = theme.spawnHighlightColor(); - const key = `${fxType}-${owner.id()}`; - let coloredCanvas: HTMLCanvasElement; - if (this.coloredAnimatedSpriteCache.has(key)) { - coloredCanvas = this.coloredAnimatedSpriteCache.get(key)!; - } else { - coloredCanvas = colorizeCanvas( - baseImage, - territoryColor, - borderColor, - spawnHighlightColor, - ); - - this.coloredAnimatedSpriteCache.set(key, coloredCanvas); - } - return coloredCanvas; - } - - private createColoredAnimatedSpriteForUnit( - fxType: FxType, - owner: PlayerView, - theme: Theme, - ): AnimatedSprite | null { - const config = ANIMATED_SPRITE_CONFIG[fxType]; - const image = this.getColoredAnimatedSprite(owner, fxType, theme); - if (!config || !image) return null; - - return new AnimatedSprite( - image, - config.frameCount, - config.frameDuration, - config.looping ?? true, - config.originX, - config.originY, - ); - } - - public createAnimatedSprite( - fxType: FxType, - owner?: PlayerView, - theme?: Theme, - ): AnimatedSprite | null { - if (owner && theme) { - return this.createColoredAnimatedSpriteForUnit(fxType, owner, theme); - } - return this.createRegularAnimatedSprite(fxType); - } -} diff --git a/src/client/graphics/fx/ConquestFx.ts b/src/client/graphics/fx/ConquestFx.ts deleted file mode 100644 index 9f45d21064..0000000000 --- a/src/client/graphics/fx/ConquestFx.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ConquestUpdate } from "../../../core/game/GameUpdates"; -import { GameView } from "../../../core/game/GameView"; -import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader"; -import { Fx, FxType } from "./Fx"; -import { FadeFx, SpriteFx } from "./SpriteFx"; - -/** - * Conquest FX: - * - conquest sprite - */ -export function conquestFxFactory( - animatedSpriteLoader: AnimatedSpriteLoader, - conquest: ConquestUpdate, - game: GameView, -): Fx { - const conquered = game.player(conquest.conqueredId); - const x = conquered.nameLocation().x; - const y = conquered.nameLocation().y; - - const swordAnimation = new SpriteFx( - animatedSpriteLoader, - x, - y, - FxType.Conquest, - 2500, - ); - return new FadeFx(swordAnimation, 0.1, 0.6); -} diff --git a/src/client/graphics/fx/Fx.ts b/src/client/graphics/fx/Fx.ts deleted file mode 100644 index d4b2066146..0000000000 --- a/src/client/graphics/fx/Fx.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface Fx { - renderTick(duration: number, ctx: CanvasRenderingContext2D): boolean; -} - -export enum FxType { - MiniFire = "MiniFire", - MiniSmoke = "MiniSmoke", - MiniBigSmoke = "MiniBigSmoke", - MiniSmokeAndFire = "MiniSmokeAndFire", - MiniExplosion = "MiniExplosion", - UnitExplosion = "UnitExplosion", - BuildingExplosion = "BuildingExplosion", - SinkingShip = "SinkingShip", - Nuke = "Nuke", - SAMExplosion = "SAMExplosion", - UnderConstruction = "UnderConstruction", - Dust = "Dust", - Conquest = "Conquest", -} diff --git a/src/client/graphics/fx/NukeFx.ts b/src/client/graphics/fx/NukeFx.ts deleted file mode 100644 index 479d68e185..0000000000 --- a/src/client/graphics/fx/NukeFx.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { GameView } from "../../../core/game/GameView"; -import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader"; -import { Fx, FxType } from "./Fx"; -import { FadeFx, SpriteFx } from "./SpriteFx"; - -/** - * Shockwave effect: draw a growing 1px white circle - */ -export class ShockwaveFx implements Fx { - private lifeTime: number = 0; - constructor( - private x: number, - private y: number, - private duration: number, - private maxRadius: number, - ) {} - - renderTick(frameTime: number, ctx: CanvasRenderingContext2D): boolean { - this.lifeTime += frameTime; - if (this.lifeTime >= this.duration) { - return false; - } - const t = this.lifeTime / this.duration; - const radius = t * this.maxRadius; - ctx.beginPath(); - ctx.arc(this.x, this.y, radius, 0, Math.PI * 2); - ctx.strokeStyle = "rgba(255, 255, 255, " + (1 - t) + ")"; - ctx.lineWidth = 0.5; - ctx.stroke(); - return true; - } -} - -/** - * Spawn @p number of @p type animation within a perimeter - */ -function addSpriteInCircle( - animatedSpriteLoader: AnimatedSpriteLoader, - x: number, - y: number, - radius: number, - num: number, - type: FxType, - result: Fx[], - game: GameView, -) { - const count = Math.max(0, Math.floor(num)); - for (let i = 0; i < count; i++) { - const angle = Math.random() * 2 * Math.PI; - const distance = Math.random() * (radius / 2); - const spawnX = Math.floor(x + Math.cos(angle) * distance); - const spawnY = Math.floor(y + Math.sin(angle) * distance); - if ( - game.isValidCoord(spawnX, spawnY) && - game.isLand(game.ref(spawnX, spawnY)) - ) { - const sprite = new FadeFx( - new SpriteFx(animatedSpriteLoader, spawnX, spawnY, type, 6000), - 0.1, - 0.8, - ); - result.push(sprite as Fx); - } - } -} - -/** - * Explosion effect: - * - explosion animation - * - shockwave - * - ruins and desolation fx - */ -export function nukeFxFactory( - animatedSpriteLoader: AnimatedSpriteLoader, - x: number, - y: number, - radius: number, - game: GameView, -): Fx[] { - const nukeFx: Fx[] = []; - // Explosion animation - nukeFx.push(new SpriteFx(animatedSpriteLoader, x, y, FxType.Nuke)); - // Shockwave animation - nukeFx.push(new ShockwaveFx(x, y, 1500, radius * 1.5)); - // Ruins and desolation sprites - const debrisPlan: Array<{ - type: FxType; - radiusFactor: number; - density: number; - }> = [ - { type: FxType.MiniFire, radiusFactor: 1.0, density: 1 / 25 }, - { type: FxType.MiniSmoke, radiusFactor: 1.0, density: 1 / 28 }, - { type: FxType.MiniBigSmoke, radiusFactor: 0.9, density: 1 / 70 }, - { type: FxType.MiniSmokeAndFire, radiusFactor: 0.9, density: 1 / 70 }, - ]; - - for (const { type, radiusFactor, density } of debrisPlan) { - addSpriteInCircle( - animatedSpriteLoader, - x, - y, - radius * radiusFactor, - radius * density, - type, - nukeFx, - game, - ); - } - return nukeFx; -} diff --git a/src/client/graphics/fx/SpriteFx.ts b/src/client/graphics/fx/SpriteFx.ts deleted file mode 100644 index cf625d7979..0000000000 --- a/src/client/graphics/fx/SpriteFx.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Theme } from "src/core/configuration/Theme"; -import { PlayerView } from "../../../core/game/GameView"; -import { AnimatedSprite } from "../AnimatedSprite"; -import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader"; -import { Fx, FxType } from "./Fx"; - -function fadeInOut( - t: number, - fadeIn: number = 0.3, - fadeOut: number = 0.7, -): number { - if (t < fadeIn) { - const f = t / fadeIn; // Map to [0, 1] - return f * f; - } else if (t < fadeOut) { - return 1; - } else { - const f = (t - fadeOut) / (1 - fadeOut); // Map to [0, 1] - return 1 - f * f; - } -} -/** - * Fade in/out another FX - */ -export class FadeFx implements Fx { - constructor( - private fxToFade: SpriteFx, - private fadeIn: number, - private fadeOut: number, - ) {} - - renderTick(duration: number, ctx: CanvasRenderingContext2D): boolean { - const t = this.fxToFade.getElapsedTime() / this.fxToFade.getDuration(); - ctx.save(); - ctx.globalAlpha = fadeInOut(t, this.fadeIn, this.fadeOut); - const result = this.fxToFade.renderTick(duration, ctx); - ctx.restore(); - return result; - } -} - -/** - * Animated sprite. Can be colored if provided an owner/theme - */ -export class SpriteFx implements Fx { - protected animatedSprite: AnimatedSprite | null; - protected elapsedTime = 0; - protected duration: number; - protected waitToTheEnd = false; - constructor( - animatedSpriteLoader: AnimatedSpriteLoader, - protected x: number, - protected y: number, - fxType: FxType, - duration?: number, - owner?: PlayerView, - theme?: Theme, - ) { - this.animatedSprite = animatedSpriteLoader.createAnimatedSprite( - fxType, - owner, - theme, - ); - if (!this.animatedSprite) { - console.error("Could not load animated sprite", fxType); - } else { - this.waitToTheEnd = duration ? true : false; - this.duration = duration ?? this.animatedSprite.lifeTime() ?? 1000; - } - } - - public setPosition(x: number, y: number): void { - this.x = x; - this.y = y; - } - - renderTick(frameTime: number, ctx: CanvasRenderingContext2D): boolean { - if (!this.animatedSprite) return false; - - this.elapsedTime += frameTime; - if (this.elapsedTime >= this.duration) return false; - - if (!this.animatedSprite.isActive() && !this.waitToTheEnd) return false; - - this.animatedSprite.update(frameTime); - this.animatedSprite.draw(ctx, this.x, this.y); - return true; - } - - getElapsedTime(): number { - return this.elapsedTime; - } - - getDuration(): number { - return this.duration; - } -} diff --git a/src/client/graphics/fx/Timeline.ts b/src/client/graphics/fx/Timeline.ts deleted file mode 100644 index 32310244e4..0000000000 --- a/src/client/graphics/fx/Timeline.ts +++ /dev/null @@ -1,33 +0,0 @@ -type TimedTask = { - delay: number; - action: () => void; - triggered: boolean; -}; - -/** - * Basic timeline to chain actions - */ -export class Timeline { - private tasks: TimedTask[] = []; - private timeElapsed = 0; - - add(delay: number, action: () => void): Timeline { - this.tasks.push({ delay, action, triggered: false }); - return this; - } - - update(dt: number) { - this.timeElapsed += dt; - - for (const task of this.tasks) { - if (!task.triggered && this.timeElapsed >= task.delay) { - task.action(); - task.triggered = true; - } - } - } - - isComplete() { - return this.tasks.every((t) => t.triggered); - } -} diff --git a/src/client/graphics/fx/UnitExplosionFx.ts b/src/client/graphics/fx/UnitExplosionFx.ts deleted file mode 100644 index b77d5f4fa7..0000000000 --- a/src/client/graphics/fx/UnitExplosionFx.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { GameView } from "../../../core/game/GameView"; -import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader"; -import { Fx, FxType } from "./Fx"; -import { SpriteFx } from "./SpriteFx"; -import { Timeline } from "./Timeline"; - -/** - * Explosion Effect: a few timed explosions - */ -export class UnitExplosionFx implements Fx { - private timeline = new Timeline(); - private explosions: Fx[] = []; - - constructor( - animatedSpriteLoader: AnimatedSpriteLoader, - private x: number, - private y: number, - game: GameView, - ) { - const config = [ - { dx: 0, dy: 0, delay: 0, type: FxType.UnitExplosion }, - { dx: 4, dy: -6, delay: 80, type: FxType.UnitExplosion }, - { dx: -6, dy: 4, delay: 160, type: FxType.UnitExplosion }, - ]; - for (const { dx, dy, delay, type } of config) { - this.timeline.add(delay, () => { - if (game.isValidCoord(x + dx, y + dy)) { - this.explosions.push( - new SpriteFx(animatedSpriteLoader, x + dx, y + dy, type), - ); - } - }); - } - } - - renderTick(frameTime: number, ctx: CanvasRenderingContext2D): boolean { - this.timeline.update(frameTime); - let allDone = true; - for (const fx of this.explosions) { - if (fx.renderTick(frameTime, ctx)) { - allDone = false; - } - } - - return !allDone || !this.timeline.isComplete(); - } -} From b27c2984fdd195dd103a6caf54907f6ec67e7655 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 13:08:08 -0700 Subject: [PATCH 24/34] include atlases/ in the public asset manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resources/atlases/ wasn't in the manifest glob list, so the build skipped hashing/copying it into static/_assets/ and the deploy pipeline's R2 uploader had no keys for it — atlases 404'd on staging. --- src/server/PublicAssetManifest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/PublicAssetManifest.ts b/src/server/PublicAssetManifest.ts index 0fc31b6a48..7e1f169f73 100644 --- a/src/server/PublicAssetManifest.ts +++ b/src/server/PublicAssetManifest.ts @@ -11,6 +11,7 @@ import { const HASHED_PUBLIC_ASSET_GLOBS = [ "changelog.md", "manifest.json", + "atlases/**/*", "cosmetics/**/*", "flags/**/*", "fonts/**/*", From 69b5a9cba26bb74b4b5f814bd40fbffc0fc3da8a Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 13:48:35 -0700 Subject: [PATCH 25/34] restore FPS tracking via self-driven RAF in PerformanceOverlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateFrameMetrics had zero callers — the canvas2D RAF loop used to invoke it per-frame, and that loop died with canvas2D. Tick metrics were unaffected since GameRenderer.tick() still calls updateTickLayerMetrics directly. The WebGL renderer doesn't expose a per-frame hook for the overlay, so the overlay now drives its own RAF, started/stopped with visibility so it stays off the hot path when hidden. --- .../graphics/layers/PerformanceOverlay.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 2f0e4134c1..78871dde53 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -82,6 +82,7 @@ export class PerformanceOverlay extends LitElement implements Controller { private fpsHistorySum: number = 0; private lastSecondTime: number = 0; private framesThisSecond: number = 0; + private fpsRafId: number | null = null; private tickExecutionTimes: number[] = []; private tickExecutionTimesSum: number = 0; private tickDelayTimes: number[] = []; @@ -519,6 +520,8 @@ export class PerformanceOverlay extends LitElement implements Controller { disconnectedCallback(): void { super.disconnectedCallback(); + this.stopFpsLoop(); + if (this.isUserSettingsListenerAttached) { globalThis.removeEventListener( `${USER_SETTINGS_CHANGED_EVENT}:${PERFORMANCE_OVERLAY_KEY}`, @@ -561,6 +564,12 @@ export class PerformanceOverlay extends LitElement implements Controller { this.isVisible = visible; FrameProfiler.setEnabled(visible); + if (visible) { + this.startFpsLoop(); + } else { + this.stopFpsLoop(); + } + if (!visible && this.resizeState) { globalThis.removeEventListener("pointermove", this.onResizePointerMove); globalThis.removeEventListener("pointerup", this.onResizePointerUp); @@ -583,6 +592,27 @@ export class PerformanceOverlay extends LitElement implements Controller { this.userSettings.setPerformanceOverlay(nextVisible); } + // FPS measurement runs on its own RAF — the WebGL renderer doesn't expose a + // per-frame hook for the overlay, and starting/stopping with visibility + // keeps the RAF cost off the hot path when the overlay is hidden. + private startFpsLoop(): void { + if (this.fpsRafId !== null) return; + const tick = () => { + this.updateFrameMetrics(0); + this.fpsRafId = requestAnimationFrame(tick); + }; + this.fpsRafId = requestAnimationFrame(tick); + } + + private stopFpsLoop(): void { + if (this.fpsRafId === null) return; + cancelAnimationFrame(this.fpsRafId); + this.fpsRafId = null; + this.lastTime = 0; + this.lastSecondTime = 0; + this.framesThisSecond = 0; + } + private onDragPointerMove = (e: PointerEvent) => { if (!this.dragState || e.pointerId !== this.dragState.pointerId) return; From b8d72d3a4e6e72a12b73924d2cd118d06f0f947c Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 14:03:58 -0700 Subject: [PATCH 26/34] set crossOrigin on WebGL atlas image loaders Atlases now load from the CDN; without crossOrigin = "anonymous" the browser refuses to texImage2D the cross-origin image. Requires the CDN to send Access-Control-Allow-Origin for /_assets/atlases/. --- src/client/render/gl/passes/conquest-popup-pass.ts | 1 + src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts | 1 + src/client/render/gl/passes/name-pass/icon-program.ts | 1 + src/client/render/gl/passes/name-pass/status-icon-program.ts | 1 + src/client/render/gl/passes/name-pass/text-program.ts | 1 + src/client/render/gl/passes/radial-menu-pass.ts | 1 + src/client/render/gl/passes/structure-level-pass.ts | 1 + src/client/render/gl/passes/structure-pass.ts | 1 + src/client/render/gl/passes/unit-pass.ts | 1 + 9 files changed, 9 insertions(+) diff --git a/src/client/render/gl/passes/conquest-popup-pass.ts b/src/client/render/gl/passes/conquest-popup-pass.ts index f45ff4c3f5..6af9909d94 100644 --- a/src/client/render/gl/passes/conquest-popup-pass.ts +++ b/src/client/render/gl/passes/conquest-popup-pass.ts @@ -207,6 +207,7 @@ export class ConquestPopupPass { private loadAtlas(): void { const img = new Image(); + img.crossOrigin = "anonymous"; img.onload = () => { const gl = this.gl; const tex = gl.createTexture()!; diff --git a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts b/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts index aeebb1ea83..42788a6267 100644 --- a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts +++ b/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts @@ -268,6 +268,7 @@ export class FxSpritePass { private async loadAtlas(): Promise { const img = new Image(); + img.crossOrigin = "anonymous"; img.src = fxAtlasUrl; await img.decode(); const gl = this.gl; diff --git a/src/client/render/gl/passes/name-pass/icon-program.ts b/src/client/render/gl/passes/name-pass/icon-program.ts index 02825dc02f..869460d346 100644 --- a/src/client/render/gl/passes/name-pass/icon-program.ts +++ b/src/client/render/gl/passes/name-pass/icon-program.ts @@ -113,6 +113,7 @@ export class IconProgram { const gl = this.gl; const load = (url: string, cb: (tex: WebGLTexture) => void) => { const img = new Image(); + img.crossOrigin = "anonymous"; img.onload = () => { const tex = gl.createTexture()!; gl.bindTexture(gl.TEXTURE_2D, tex); diff --git a/src/client/render/gl/passes/name-pass/status-icon-program.ts b/src/client/render/gl/passes/name-pass/status-icon-program.ts index 07db99d6b8..40f81392f9 100644 --- a/src/client/render/gl/passes/name-pass/status-icon-program.ts +++ b/src/client/render/gl/passes/name-pass/status-icon-program.ts @@ -105,6 +105,7 @@ export class StatusIconProgram { private loadAtlas(): void { const gl = this.gl; const img = new Image(); + img.crossOrigin = "anonymous"; img.onload = () => { const tex = gl.createTexture()!; gl.bindTexture(gl.TEXTURE_2D, tex); diff --git a/src/client/render/gl/passes/name-pass/text-program.ts b/src/client/render/gl/passes/name-pass/text-program.ts index 8c38050908..963292f5ae 100644 --- a/src/client/render/gl/passes/name-pass/text-program.ts +++ b/src/client/render/gl/passes/name-pass/text-program.ts @@ -127,6 +127,7 @@ export class TextProgram { private loadAtlas(): void { const gl = this.gl; const img = new Image(); + img.crossOrigin = "anonymous"; img.onload = () => { const tex = gl.createTexture()!; gl.bindTexture(gl.TEXTURE_2D, tex); diff --git a/src/client/render/gl/passes/radial-menu-pass.ts b/src/client/render/gl/passes/radial-menu-pass.ts index 2b95965882..b394550ed6 100644 --- a/src/client/render/gl/passes/radial-menu-pass.ts +++ b/src/client/render/gl/passes/radial-menu-pass.ts @@ -229,6 +229,7 @@ export class RadialMenuPass { private loadEmojiAtlas(): void { const img = new Image(); + img.crossOrigin = "anonymous"; img.onload = () => { this.atlasImg = img; this.rebuildAtlasTexture(); diff --git a/src/client/render/gl/passes/structure-level-pass.ts b/src/client/render/gl/passes/structure-level-pass.ts index b50cf29f8a..04f07605c1 100644 --- a/src/client/render/gl/passes/structure-level-pass.ts +++ b/src/client/render/gl/passes/structure-level-pass.ts @@ -210,6 +210,7 @@ export class StructureLevelPass { private loadAtlas(): void { const img = new Image(); + img.crossOrigin = "anonymous"; img.onload = () => { const gl = this.gl; const tex = gl.createTexture()!; diff --git a/src/client/render/gl/passes/structure-pass.ts b/src/client/render/gl/passes/structure-pass.ts index 836bb7880b..3e14d9d474 100644 --- a/src/client/render/gl/passes/structure-pass.ts +++ b/src/client/render/gl/passes/structure-pass.ts @@ -245,6 +245,7 @@ export class StructurePass { private async loadAtlas(): Promise { const img = new Image(); + img.crossOrigin = "anonymous"; img.src = iconAtlasUrl; await img.decode(); const gl = this.gl; diff --git a/src/client/render/gl/passes/unit-pass.ts b/src/client/render/gl/passes/unit-pass.ts index 2b88039846..3d62851ecd 100644 --- a/src/client/render/gl/passes/unit-pass.ts +++ b/src/client/render/gl/passes/unit-pass.ts @@ -301,6 +301,7 @@ export class UnitPass { private async loadAtlas(): Promise { const img = new Image(); + img.crossOrigin = "anonymous"; img.src = unitAtlasUrl; await img.decode(); const gl = this.gl; From 4dc4810bcca70de28e9bfc0cce1a10b506ef00fd Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 19:13:29 -0700 Subject: [PATCH 27/34] render build ghost at cursor with sub-tile precision Bypass the snap-to-tile in TransformHandler by adding screenToWorldCoordinatesFloat. Each render frame, BuildPreviewController re-emits the ghost preview at the cursor's exact world position (adjusted by -0.5 to cancel the shader's tile-center offset). Buildable validation still runs on the snapped tile at the 50ms throttle, but the icon now follows the cursor 1:1 instead of stepping tile-to-tile. --- src/client/TransformHandler.ts | 28 ++++++++----- .../controllers/BuildPreviewController.ts | 41 ++++++++++++++++++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/client/TransformHandler.ts b/src/client/TransformHandler.ts index c1175b7882..abab408380 100644 --- a/src/client/TransformHandler.ts +++ b/src/client/TransformHandler.ts @@ -115,17 +115,25 @@ export class TransformHandler { } screenToWorldCoordinates(screenX: number, screenY: number): Cell { - const canvasCoords = this.screenToCanvasCoordinates(screenX, screenY); - - const centerX = - (canvasCoords.x - this.game.width() / 2) / this.scale + this.offsetX; - const centerY = - (canvasCoords.y - this.game.height() / 2) / this.scale + this.offsetY; - - const gameX = centerX + this.game.width() / 2; - const gameY = centerY + this.game.height() / 2; + const f = this.screenToWorldCoordinatesFloat(screenX, screenY); + return new Cell(Math.floor(f.x), Math.floor(f.y)); + } - return new Cell(Math.floor(gameX), Math.floor(gameY)); + /** Like screenToWorldCoordinates but returns sub-tile precision. */ + screenToWorldCoordinatesFloat( + screenX: number, + screenY: number, + ): { x: number; y: number } { + const canvasCoords = this.screenToCanvasCoordinates(screenX, screenY); + const gameX = + (canvasCoords.x - this.game.width() / 2) / this.scale + + this.offsetX + + this.game.width() / 2; + const gameY = + (canvasCoords.y - this.game.height() / 2) / this.scale + + this.offsetY + + this.game.height() / 2; + return { x: gameX, y: gameY }; } canvasToScreenCoordinates( diff --git a/src/client/controllers/BuildPreviewController.ts b/src/client/controllers/BuildPreviewController.ts index 21a9398a16..c81443ee1c 100644 --- a/src/client/controllers/BuildPreviewController.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -44,6 +44,12 @@ export class BuildPreviewController implements Controller { private lastGhostQueryAt: number = 0; private pendingConfirm: MouseUpEvent | null = null; + // Buildable validation runs on the snapped tile under the cursor, but the + // rendered icon follows the cursor at sub-tile precision so motion is + // continuous instead of stepping tile-to-tile. cursorLoop re-emits each + // frame with the current cursor world position. + private lastGhostData: GhostPreviewData | null = null; + constructor( private game: GameView, private eventBus: EventBus, @@ -60,6 +66,29 @@ export class BuildPreviewController implements Controller { new MouseUpEvent(this.mousePos.x, this.mousePos.y), ), ); + + // Re-emit the ghost each render frame at the cursor's current world + // position (sub-tile). Buildable validation still runs on the snapped + // tile in renderGhost(); this loop just keeps the icon under the cursor + // so motion is continuous instead of stepping tile-to-tile. + // The shader treats (tileX + 0.5, tileY + 0.5) as the icon center (so an + // integer tile coord centers on that tile), so we subtract 0.5 here to + // place the icon exactly under the cursor. + const cursorLoop = () => { + if (this.lastGhostData !== null) { + const w = this.transformHandler.screenToWorldCoordinatesFloat( + this.mousePos.x, + this.mousePos.y, + ); + this.view.updateGhostPreview({ + ...this.lastGhostData, + tileX: w.x - 0.5, + tileY: w.y - 0.5, + }); + } + requestAnimationFrame(cursorLoop); + }; + requestAnimationFrame(cursorLoop); } tick() { @@ -185,10 +214,17 @@ export class BuildPreviewController implements Controller { /** * Push a GhostPreviewData snapshot to the WebGL view (StructurePass / * RangeCirclePass / RailroadPass / CrosshairPass all read it). null when - * the ghost can't be placed. + * the ghost can't be placed. smoothLoop interpolates displayed position + * toward the target tile each frame. */ private emitGhostPreview(tileRef: TileRef | undefined): void { - this.view.updateGhostPreview(this.buildGhostPreviewData(tileRef)); + const data = this.buildGhostPreviewData(tileRef); + if (data === null) { + this.lastGhostData = null; + this.view.updateGhostPreview(null); + return; + } + this.lastGhostData = data; } private buildGhostPreviewData( @@ -307,6 +343,7 @@ export class BuildPreviewController implements Controller { this.pendingConfirm = null; this.ghostUnit = null; this.uiState.ghostRailPaths = []; + this.lastGhostData = null; this.view.updateGhostPreview(null); } From 3eedaf7bbc0bfa377768e21e56e0f3a4d386e658 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 19:37:07 -0700 Subject: [PATCH 28/34] wire nuke trajectory + blast radius into the build ghost preview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NukeTrajectoryPass and the rangeRadius pipe existed but had no caller — trajectory arc and outer-blast circle never appeared during build mode. BuildPreviewController now picks the closest active player silo as the launch source, collects non-allied SAMs as threats, and pushes a NukeTrajectoryData each preview tick. rangeRadius is set to nukeMagnitudes(type).outer for AtomBomb / HydrogenBomb so the existing RangeCirclePass renders the blast radius at the target. --- .../controllers/BuildPreviewController.ts | 93 ++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/src/client/controllers/BuildPreviewController.ts b/src/client/controllers/BuildPreviewController.ts index c81443ee1c..f4de2d842c 100644 --- a/src/client/controllers/BuildPreviewController.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -22,7 +22,8 @@ import { MouseMoveEvent, MouseUpEvent, } from "../InputHandler"; -import { GameView as WebGLGameView } from "../render/gl"; +import { GameView as WebGLGameView, buildNukeTrajectory } from "../render/gl"; +import type { SAMInfo } from "../render/gl/utils/nuke-trajectory"; import type { GhostPreviewData } from "../render/types"; import { TransformHandler } from "../TransformHandler"; import { @@ -222,9 +223,88 @@ export class BuildPreviewController implements Controller { if (data === null) { this.lastGhostData = null; this.view.updateGhostPreview(null); + } else { + this.lastGhostData = data; + } + this.updateNukeTrajectoryPreview(tileRef); + } + + /** + * For AtomBomb / HydrogenBomb ghosts, push the Bezier trajectory preview + * (closest player-owned silo → target, accounting for non-allied SAMs). + * Cleared whenever the ghost isn't a nuke, has no target, or the player + * has no silos. + */ + private updateNukeTrajectoryPreview(tileRef: TileRef | undefined): void { + if (!this.ghostUnit || tileRef === undefined) { + this.view.updateNukeTrajectory(null); return; } - this.lastGhostData = data; + const type = this.ghostUnit.buildableUnit.type; + if (type !== UnitType.AtomBomb && type !== UnitType.HydrogenBomb) { + this.view.updateNukeTrajectory(null); + return; + } + const myPlayer = this.game.myPlayer(); + if (!myPlayer) { + this.view.updateNukeTrajectory(null); + return; + } + + const silos = myPlayer + .units(UnitType.MissileSilo) + .filter((u) => u.isActive()); + if (silos.length === 0) { + this.view.updateNukeTrajectory(null); + return; + } + + const dstX = this.game.x(tileRef); + const dstY = this.game.y(tileRef); + let bestSilo = silos[0]; + let bestDistSq = Infinity; + for (const s of silos) { + const sx = this.game.x(s.tile()); + const sy = this.game.y(s.tile()); + const dx = sx - dstX; + const dy = sy - dstY; + const d = dx * dx + dy * dy; + if (d < bestDistSq) { + bestDistSq = d; + bestSilo = s; + } + } + const srcX = this.game.x(bestSilo.tile()); + const srcY = this.game.y(bestSilo.tile()); + + // Non-allied SAMs threaten the trajectory; own + allied SAMs don't. + const allyIds = new Set(); + for (const a of myPlayer.allies()) allyIds.add(a.smallID()); + const sams: SAMInfo[] = []; + for (const s of this.game.units(UnitType.SAMLauncher)) { + if (!s.isActive()) continue; + const owner = s.owner(); + if (owner === myPlayer) continue; + if (allyIds.has(owner.smallID())) continue; + const r = this.game.config().samRange(s.level()); + sams.push({ + x: this.game.x(s.tile()), + y: this.game.y(s.tile()), + rangeSq: r * r, + }); + } + + this.view.updateNukeTrajectory( + buildNukeTrajectory( + srcX, + srcY, + dstX, + dstY, + this.game.height(), + this.uiState.rocketDirectionUp, + sams, + ), + ); } private buildGhostPreviewData( @@ -243,11 +323,17 @@ export class BuildPreviewController implements Controller { upgradeTargetTile = this.game.unit(u.canUpgrade)?.tile() ?? null; } - // Range circle: only meaningful for SAM placement preview. + // Range circle: SAM placement preview shows targetable radius; nuke + // previews show the outer blast radius at the target tile. let rangeRadius = 0; if (u.type === UnitType.SAMLauncher) { const level = this.resolveGhostRangeLevel(u) ?? 1; rangeRadius = this.game.config().samRange(level); + } else if ( + u.type === UnitType.AtomBomb || + u.type === UnitType.HydrogenBomb + ) { + rangeRadius = this.game.config().nukeMagnitudes(u.type).outer; } return { @@ -345,6 +431,7 @@ export class BuildPreviewController implements Controller { this.uiState.ghostRailPaths = []; this.lastGhostData = null; this.view.updateGhostPreview(null); + this.view.updateNukeTrajectory(null); } private removeGhostStructure() { From c197f5864f0c1ba26c48bf1545134c6ae0dbdad8 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 20:01:23 -0700 Subject: [PATCH 29/34] replace day/night cycle with a binary light/dark mode tied to UserSettings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cycling sun/moon animation was distracting and not a fan favorite. Drops the cycle path entirely — RenderSettings.dayNight.mode is now "light" | "dark", and the cycle-only fields (cycleTicks, startPhase, noonHold, nightHold) plus the passEnabled.dayNight toggle are gone. getAmbient is a one-liner. The in-game mode follows the existing darkMode UserSetting (same one that drives the page-level CSS class); ClientGameRunner applies it on startup and on the per-key change event. --- src/client/ClientGameRunner.ts | 18 +++++++- src/client/render/gl/debug/layout.ts | 21 +--------- .../render/gl/passes/night-composite-pass.ts | 42 ++----------------- src/client/render/gl/render-settings.json | 7 +--- src/client/render/gl/render-settings.ts | 7 +--- src/client/render/gl/renderer.ts | 15 ++----- 6 files changed, 27 insertions(+), 83 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 26b61103e4..db807385f1 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -29,7 +29,11 @@ import { } from "../core/game/GameUpdates"; import { GameView, PlayerView } from "../core/game/GameView"; import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader"; -import { UserSettings } from "../core/game/UserSettings"; +import { + DARK_MODE_KEY, + USER_SETTINGS_CHANGED_EVENT, + UserSettings, +} from "../core/game/UserSettings"; import { WorkerClient } from "../core/worker/WorkerClient"; import { getPersistentID } from "./Auth"; import { @@ -429,6 +433,18 @@ async function createClientGame( const { view, glCanvas, cachedWebGLFrameCallback } = createWebGLView(gameMap); + // Bind the WebGL renderer's day/night mode to the existing darkMode + // UserSetting so the in-game map matches the rest of the UI. Initial + // apply + live updates via the per-key settings-changed event. + const applyDayNightMode = (isDark: boolean): void => { + view.getSettings().dayNight.mode = isDark ? "dark" : "light"; + }; + applyDayNightMode(userSettings.darkMode()); + globalThis.addEventListener( + `${USER_SETTINGS_CHANGED_EVENT}:${DARK_MODE_KEY}`, + (e) => applyDayNightMode((e as CustomEvent).detail === "true"), + ); + const gameRenderer = createRenderer( inputOverlay, gameView, diff --git a/src/client/render/gl/debug/layout.ts b/src/client/render/gl/debug/layout.ts index 9bfd49a812..14551bf2ea 100644 --- a/src/client/render/gl/debug/layout.ts +++ b/src/client/render/gl/debug/layout.ts @@ -18,7 +18,6 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { toggle(s.passEnabled, "railroad", d.passEnabled), toggle(s.passEnabled, "fx", d.passEnabled), toggle(s.passEnabled, "bar", d.passEnabled), - toggle(s.passEnabled, "dayNight", d.passEnabled), toggle(s.passEnabled, "nameDebug", d.passEnabled, "Name Debug Boxes"), ]), @@ -50,25 +49,7 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { ]), folder("Day / Night", [ - select( - s.dayNight, - "mode", - d.dayNight, - ["light", "dark", "cycle"], - "Mode", - ), - slider(s.dayNight, "cycleTicks", d.dayNight, 60, 6000, 10), - slider( - s.dayNight, - "startPhase", - d.dayNight, - 0, - 1, - 0.01, - "Start Phase (0=noon)", - ), - slider(s.dayNight, "noonHold", d.dayNight, 0, 1, 0.01, "Noon Hold"), - slider(s.dayNight, "nightHold", d.dayNight, 0, 1, 0.01, "Night Hold"), + select(s.dayNight, "mode", d.dayNight, ["light", "dark"], "Mode"), slider(s.dayNight, "nightAmbient", d.dayNight, 0, 1, 0.01), slider(s.dayNight, "dayAmbient", d.dayNight, 0, 1, 0.01), slider(s.dayNight, "falloffPower", d.dayNight, 0.5, 5, 0.1), diff --git a/src/client/render/gl/passes/night-composite-pass.ts b/src/client/render/gl/passes/night-composite-pass.ts index b87146bd4d..a8616fafbd 100644 --- a/src/client/render/gl/passes/night-composite-pass.ts +++ b/src/client/render/gl/passes/night-composite-pass.ts @@ -15,11 +15,6 @@ import { createFullscreenQuad, createProgram } from "../utils/gl-utils"; import compositeFragSrc from "../shaders/day-night/composite.frag.glsl?raw"; import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw"; -function smoothstep(edge0: number, edge1: number, x: number): number { - const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0))); - return t * t * (3 - 2 * t); -} - export class NightCompositePass { private gl: WebGL2RenderingContext; private settings: RenderSettings; @@ -51,38 +46,9 @@ export class NightCompositePass { // Ambient // ------------------------------------------------------------------------- - getAmbient(tick: number): number { + getAmbient(): number { const dn = this.settings.dayNight; - - if (dn.mode === "light") return dn.dayAmbient; - if (dn.mode === "dark") return dn.nightAmbient; - - // Normalize phase to [0, 1), 0 = noon - const phase = (((tick / dn.cycleTicks + dn.startPhase) % 1) + 1) % 1; - - // Clamp holds so they never exceed the full cycle - const noonHold = Math.min(dn.noonHold, 1); - const nightHold = Math.min(dn.nightHold, Math.max(0, 1 - noonHold)); - const halfTransition = (1 - noonHold - nightHold) / 2; - - // Region boundaries (all in [0, 1)) - const duskStart = noonHold / 2; - const duskEnd = duskStart + halfTransition; // = 0.5 - nightHold/2 - const nightEnd = duskEnd + nightHold; // = 0.5 + nightHold/2 - const dawnEnd = nightEnd + halfTransition; // = 1 - noonHold/2 - - let t: number; - if (phase < duskStart || phase >= dawnEnd) { - t = 1; // noon hold - } else if (phase < duskEnd) { - t = smoothstep(duskEnd, duskStart, phase); // day → night - } else if (phase < nightEnd) { - t = 0; // midnight hold - } else { - t = smoothstep(nightEnd, dawnEnd, phase); // night → day - } - - return dn.nightAmbient + (dn.dayAmbient - dn.nightAmbient) * t; + return dn.mode === "dark" ? dn.nightAmbient : dn.dayAmbient; } // ------------------------------------------------------------------------- @@ -90,12 +56,12 @@ export class NightCompositePass { // ------------------------------------------------------------------------- /** Pure combiner — receives captured scene + lightmap textures, outputs to screen. */ - draw(tick: number, sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void { + draw(sceneTex: WebGLTexture, lightmapTex: WebGLTexture): void { const gl = this.gl; gl.disable(gl.BLEND); gl.useProgram(this.compositeProg); - gl.uniform1f(this.uCompositeAmbient, this.getAmbient(tick)); + gl.uniform1f(this.uCompositeAmbient, this.getAmbient()); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, sceneTex); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index 2a0105c4cc..e38b0e3c17 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -9,7 +9,6 @@ "railroad": true, "fx": true, "bar": true, - "dayNight": true, "nameDebug": false }, "falloutBloom": { @@ -34,11 +33,7 @@ "heatDecayPerTick": 1 }, "dayNight": { - "mode": "cycle", - "cycleTicks": 6000, - "startPhase": 0, - "noonHold": 0.25, - "nightHold": 0.1, + "mode": "light", "nightAmbient": 0.15, "dayAmbient": 1, "falloffPower": 2, diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts index 1b661dea93..d9bda1b204 100644 --- a/src/client/render/gl/render-settings.ts +++ b/src/client/render/gl/render-settings.ts @@ -11,7 +11,6 @@ export interface RenderSettings { railroad: boolean; fx: boolean; bar: boolean; - dayNight: boolean; nameDebug: boolean; }; falloutBloom: { @@ -36,11 +35,7 @@ export interface RenderSettings { heatDecayPerTick: number; }; dayNight: { - mode: "light" | "dark" | "cycle"; - cycleTicks: number; - startPhase: number; // 0–1, where 0 = noon, 0.25 = dusk, 0.5 = midnight, 0.75 = dawn - noonHold: number; // fraction of cycle held at full brightness (0–1) - nightHold: number; // fraction of cycle held at full darkness (0–1); noonHold+nightHold ≤ 1 + mode: "light" | "dark"; nightAmbient: number; dayAmbient: number; falloffPower: number; diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/renderer.ts index 67775ff0b7..a1e831caee 100644 --- a/src/client/render/gl/renderer.ts +++ b/src/client/render/gl/renderer.ts @@ -135,7 +135,6 @@ export class GPURenderer { private animId: number | null = null; private frameTick = 0; - private gameTick = 0; private mapW = 0; private mapH = 0; @@ -598,7 +597,6 @@ export class GPURenderer { updateUnits(units: Map, gameTick: number): void { this.lastUnits = units; this.frameTick++; - this.gameTick = gameTick; this.unitPass.updateUnits(units, this.frameTick); this.barPass.updateBars(units, this.lastStructures, gameTick); this.pointLightPass.updateLights(units); @@ -1015,7 +1013,7 @@ export class GPURenderer { ); const lightTex = this.lightmapPass.draw(cam, cw, ch); toScreen(this.gl, cw, ch, () => - this.nightCompositePass.draw(this.gameTick, sceneTex, lightTex), + this.nightCompositePass.draw(sceneTex, lightTex), ); } else { toScreen(this.gl, cw, ch, () => this.drawBaseLayer(cam)); @@ -1025,11 +1023,7 @@ export class GPURenderer { } private isNightActive(): boolean { - const mode = this.settings.dayNight.mode; - return ( - mode === "dark" || - (mode === "cycle" && this.settings.passEnabled.dayNight) - ); + return this.settings.dayNight.mode === "dark"; } private resizeSceneTargetIfNeeded(cw: number, ch: number): void { @@ -1099,10 +1093,7 @@ export class GPURenderer { if (this.gridView) this.coordinateGridPass.draw(cam, zoom); if (pe.name && !this.gridView) - this.namePass.draw( - cam, - this.nightCompositePass.getAmbient(this.gameTick), - ); + this.namePass.draw(cam, this.nightCompositePass.getAmbient()); this.radialMenuPass.draw(); From fb45c27d829707a3298bd327fbcaddda2094a074 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 20:35:22 -0700 Subject: [PATCH 30/34] add subtle player-tile highlight on nation hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hover wiring already pushed setHighlightOwner into the border pass, but the WebGL canvas has pointer-events: none (post-migration to the inputOverlay div) so MapInteraction's pointermove listener never fired. Forward pointermove from the input overlay to view.handlePointerMove so hover actually triggers. While there, brighten every tile owned by the hovered player — the territory frag shader now reads uHighlightOwner / uHighlightBrighten and mixes toward white when the tile owner matches. Wired through territory-pass.ts; renderer.setHighlightOwner forwards to both border and territory passes. New highlightFillBrighten setting (0.15) keeps the fill tint tunable independently of the existing highlightBrighten border setting, which is dropped from 0.6 → 0.25 so neither effect blows out. --- src/client/ClientGameRunner.ts | 7 +++++++ src/client/WebGLFrameBuilder.ts | 12 ++++++++++++ src/client/render/gl/debug/layout.ts | 11 ++++++++++- src/client/render/gl/game-view.ts | 12 ++++++++++++ src/client/render/gl/passes/territory-pass.ts | 18 ++++++++++++++++++ src/client/render/gl/render-settings.json | 3 ++- src/client/render/gl/render-settings.ts | 1 + src/client/render/gl/renderer.ts | 5 +---- .../gl/shaders/map-overlay/territory.frag.glsl | 11 ++++++++++- 9 files changed, 73 insertions(+), 7 deletions(-) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index db807385f1..934e5bb4d5 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -445,6 +445,13 @@ async function createClientGame( (e) => applyDayNightMode((e as CustomEvent).detail === "true"), ); + // The WebGL canvas has pointer-events: none so input flows through the + // overlay div. Forward pointermove to the WebGL view's MapInteraction so + // hover-driven features (highlight owner, etc.) still work. + inputOverlay.addEventListener("pointermove", (e) => + view.handlePointerMove(e), + ); + const gameRenderer = createRenderer( inputOverlay, gameView, diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index 5393b9279f..b98228ef29 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -19,6 +19,10 @@ const PALETTE_SIZE = 4096; export class WebGLFrameBuilder { private readonly palette: Float32Array; private readonly knownSmallIDs = new Set(); + // The renderer needs to know which player is "me" so affiliation tint, + // unit colors, and SAM-radius perspective work. Push it once the local + // player's update arrives (may take several ticks during join). + private localPlayerSmallID = 0; constructor(private readonly view: WebGLGameView) { this.palette = new Float32Array(PALETTE_SIZE * 2 * 4); @@ -26,9 +30,17 @@ export class WebGLFrameBuilder { update(gameView: GameView): void { this.syncPlayers(gameView); + this.syncLocalPlayer(gameView); uploadFrameData(this.view, gameView.frameData()); } + private syncLocalPlayer(gameView: GameView): void { + const sid = gameView.myPlayer()?.smallID() ?? 0; + if (sid === this.localPlayerSmallID) return; + this.localPlayerSmallID = sid; + this.view.setLocalPlayerID(sid); + } + private syncPlayers(gameView: GameView): void { const newPlayers: PlayerStatic[] = []; for (const p of gameView.players()) { diff --git a/src/client/render/gl/debug/layout.ts b/src/client/render/gl/debug/layout.ts index 14551bf2ea..e94f2ef9bd 100644 --- a/src/client/render/gl/debug/layout.ts +++ b/src/client/render/gl/debug/layout.ts @@ -116,7 +116,16 @@ export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { 0, 1, 0.01, - "Highlight Brighten", + "Highlight Brighten (border)", + ), + slider( + s.mapOverlay, + "highlightFillBrighten", + d.mapOverlay, + 0, + 1, + 0.01, + "Highlight Brighten (fill)", ), slider( s.mapOverlay, diff --git a/src/client/render/gl/game-view.ts b/src/client/render/gl/game-view.ts index 6786d9cc12..48d7f0306a 100644 --- a/src/client/render/gl/game-view.ts +++ b/src/client/render/gl/game-view.ts @@ -110,6 +110,18 @@ export class GameView { if (rect.width > 0) this.renderer.resize(rect.width, rect.height); } + /** + * Forward a pointermove event into the MapInteraction handler. The WebGL + * canvas itself has pointer-events: none (input flows through a separate + * overlay div in the main client), so the listener bound to `canvas` in + * the constructor never actually fires for game-mode input. Callers that + * own the active input element forward pointermove events here so hover + * tracking + setHighlightOwner still work. + */ + handlePointerMove(e: PointerEvent): void { + this.interaction.handlePointerMove(e); + } + // ---- Event system ---- on( diff --git a/src/client/render/gl/passes/territory-pass.ts b/src/client/render/gl/passes/territory-pass.ts index c2e7485758..63c0a664ad 100644 --- a/src/client/render/gl/passes/territory-pass.ts +++ b/src/client/render/gl/passes/territory-pass.ts @@ -34,6 +34,9 @@ export class TerritoryPass { private uCharcoalBase: WebGLUniformLocation; private uCharcoalVariation: WebGLUniformLocation; private uCharcoalAlpha: WebGLUniformLocation; + private uHighlightOwner: WebGLUniformLocation; + private uHighlightBrighten: WebGLUniformLocation; + private highlightOwner = 0; private vao: WebGLVertexArrayObject; private tileTex: WebGLTexture; @@ -101,6 +104,14 @@ export class TerritoryPass { this.program, "uCharcoalAlpha", )!; + this.uHighlightOwner = gl.getUniformLocation( + this.program, + "uHighlightOwner", + )!; + this.uHighlightBrighten = gl.getUniformLocation( + this.program, + "uHighlightBrighten", + )!; gl.useProgram(this.program); gl.uniform1i(gl.getUniformLocation(this.program, "uTileTex"), 0); @@ -319,6 +330,11 @@ export class TerritoryPass { this.altView = active; } + /** Set the hovered player's smallID for territory-fill brightening (0 = off). */ + setHighlightOwner(ownerID: number): void { + this.highlightOwner = ownerID; + } + /** Draw territory fill + fallout charcoal. Blending must be enabled by caller. */ draw(cameraMatrix: Float32Array): void { this.flushTileTexture(); @@ -334,6 +350,8 @@ export class TerritoryPass { gl.uniform1f(this.uCharcoalBase, mo.charcoalBase); gl.uniform1f(this.uCharcoalVariation, mo.charcoalVariation); gl.uniform1f(this.uCharcoalAlpha, mo.charcoalAlpha); + gl.uniform1ui(this.uHighlightOwner, this.highlightOwner); + gl.uniform1f(this.uHighlightBrighten, mo.highlightFillBrighten); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.tileTex); diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index e38b0e3c17..e7d2058ecf 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -65,7 +65,8 @@ "emberColorBrightG": 0.5, "emberColorBrightB": 0.05, "emberStrengthUnowned": 0.5, - "highlightBrighten": 0.6, + "highlightBrighten": 0.25, + "highlightFillBrighten": 0.15, "highlightThicken": 2, "defensePostRange": 30, "embargoTintRatio": 0.35, diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/render-settings.ts index d9bda1b204..2aa9cfc601 100644 --- a/src/client/render/gl/render-settings.ts +++ b/src/client/render/gl/render-settings.ts @@ -68,6 +68,7 @@ export interface RenderSettings { emberColorBrightB: number; emberStrengthUnowned: number; highlightBrighten: number; + highlightFillBrighten: number; highlightThicken: number; defensePostRange: number; embargoTintRatio: number; diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/renderer.ts index a1e831caee..d715217122 100644 --- a/src/client/render/gl/renderer.ts +++ b/src/client/render/gl/renderer.ts @@ -694,10 +694,6 @@ export class GPURenderer { this.railroadPass.updateGhostPreview(data); this.rangeCirclePass.updateGhostPreview(data); this.crosshairPass.updateGhostPreview(data); - if (data) this.localPlayerID = data.ownerID; - this.samRadiusPass.setLocalPlayer(this.localPlayerID); - this.affiliationPalette.setLocalPlayer(this.localPlayerID); - this.unitPass.setLocalPlayer(this.localPlayerID); this.samGhostVisible = data !== null && SAM_RADIUS_GHOST_TYPES.has(data.ghostType); this.samRadiusPass.setVisible( @@ -723,6 +719,7 @@ export class GPURenderer { setHighlightOwner(ownerID: number): void { this.borderPass.setHighlightOwner(ownerID); + this.territoryPass.setHighlightOwner(ownerID); } setHighlightStructureTypes(unitTypes: string[] | null): void { this.structurePass.setHighlightTypes(unitTypes); diff --git a/src/client/render/gl/shaders/map-overlay/territory.frag.glsl b/src/client/render/gl/shaders/map-overlay/territory.frag.glsl index 94c0579b47..91664f4b49 100644 --- a/src/client/render/gl/shaders/map-overlay/territory.frag.glsl +++ b/src/client/render/gl/shaders/map-overlay/territory.frag.glsl @@ -10,6 +10,8 @@ uniform int uAltView; uniform float uCharcoalBase; uniform float uCharcoalVariation; uniform float uCharcoalAlpha; +uniform uint uHighlightOwner; // 0 = no highlight; otherwise smallID of hovered owner +uniform float uHighlightBrighten; // mix amount toward white for highlighted tiles in vec2 vWorldPos; out vec4 fragColor; @@ -38,5 +40,12 @@ void main() { // --- Territory fill (owned) --- float u = (float(owner) + 0.5) / float(PALETTE_SIZE); - fragColor = texture(uPalette, vec2(u, 0.25)); + vec4 color = texture(uPalette, vec2(u, 0.25)); + + // Hover highlight: brighten every tile owned by the hovered player. + if (uHighlightOwner != 0u && owner == uHighlightOwner) { + color.rgb = mix(color.rgb, vec3(1.0), uHighlightBrighten); + } + + fragColor = color; } From 5a9694e2bd9b1a8872748aeba127572a77be9984 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 20:46:02 -0700 Subject: [PATCH 31/34] replace MapInteraction with HoverHighlightController; one input system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MapInteraction bound DOM events to the WebGL canvas, but the canvas has pointer-events: none post-migration so its pointermove/down/up/wheel/ keydown listeners never fired — duplicating InputHandler (which owns the inputOverlay div + EventBus pipeline) and leaving most features dead. The one alive bit was hover→setHighlightOwner, which I'd manually forwarded as a workaround. Now there's a HoverHighlightController that listens to MouseMoveEvent, computes the cursor's tile owner, and pushes setHighlightOwner. Delete map-interaction.ts (418 lines) + keyboard-pan.ts, trim the DOM-binding constructor + proxy methods (setFitZoomOnDoubleClick, setPanSpeed, setZoomSpeed, etc.) out of GameView, and drop the ClientGameRunner pointermove forwarder. Input flows through one path: DOM → inputOverlay → InputHandler → EventBus → controllers/renderer. --- src/client/ClientGameRunner.ts | 7 - .../controllers/HoverHighlightController.ts | 44 ++ src/client/graphics/GameRenderer.ts | 2 + src/client/render/gl/game-view.ts | 76 ---- src/client/render/gl/index.ts | 2 - src/client/render/gl/keyboard-pan.ts | 141 ------ src/client/render/gl/map-interaction.ts | 418 ------------------ 7 files changed, 46 insertions(+), 644 deletions(-) create mode 100644 src/client/controllers/HoverHighlightController.ts delete mode 100644 src/client/render/gl/keyboard-pan.ts delete mode 100644 src/client/render/gl/map-interaction.ts diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 934e5bb4d5..db807385f1 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -445,13 +445,6 @@ async function createClientGame( (e) => applyDayNightMode((e as CustomEvent).detail === "true"), ); - // The WebGL canvas has pointer-events: none so input flows through the - // overlay div. Forward pointermove to the WebGL view's MapInteraction so - // hover-driven features (highlight owner, etc.) still work. - inputOverlay.addEventListener("pointermove", (e) => - view.handlePointerMove(e), - ); - const gameRenderer = createRenderer( inputOverlay, gameView, diff --git a/src/client/controllers/HoverHighlightController.ts b/src/client/controllers/HoverHighlightController.ts new file mode 100644 index 0000000000..9a0e35aa2e --- /dev/null +++ b/src/client/controllers/HoverHighlightController.ts @@ -0,0 +1,44 @@ +/** + * HoverHighlightController — pushes the cursor's tile-owner to the WebGL + * view so the territory + border passes can highlight the hovered player. + * + * Replaces the hover path inside the renderer's MapInteraction class (which + * was bound to the WebGL canvas; that canvas has pointer-events: none in the + * current input architecture so its listeners never fired). All input flows + * through InputHandler → MouseMoveEvent on the EventBus, so we just listen. + */ + +import { EventBus } from "../../core/EventBus"; +import { GameView } from "../../core/game/GameView"; +import { Controller } from "../Controller"; +import { MouseMoveEvent } from "../InputHandler"; +import { GameView as WebGLGameView } from "../render/gl"; +import { OWNER_MASK } from "../render/gl/utils/tile-codec"; +import { TransformHandler } from "../TransformHandler"; + +export class HoverHighlightController implements Controller { + private lastOwnerID = 0; + + constructor( + private game: GameView, + private eventBus: EventBus, + private transformHandler: TransformHandler, + private view: WebGLGameView, + ) {} + + init() { + this.eventBus.on(MouseMoveEvent, (e) => this.onMouseMove(e)); + } + + private onMouseMove(e: MouseMoveEvent): void { + const cell = this.transformHandler.screenToWorldCoordinates(e.x, e.y); + let ownerID = 0; + if (this.game.isValidCoord(cell.x, cell.y)) { + const ref = this.game.ref(cell.x, cell.y); + ownerID = this.game.tileState(ref) & OWNER_MASK; + } + if (ownerID === this.lastOwnerID) return; + this.lastOwnerID = ownerID; + this.view.setHighlightOwner(ownerID); + } +} diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index c8c854a678..1beae5d95d 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -6,6 +6,7 @@ import { GameStartingModal } from "../GameStartingModal"; import { TransformHandler } from "../TransformHandler"; import { UIState } from "../UIState"; import { BuildPreviewController } from "../controllers/BuildPreviewController"; +import { HoverHighlightController } from "../controllers/HoverHighlightController"; import { WarshipSelectionController } from "../controllers/WarshipSelectionController"; import { GameView as WebGLGameView } from "../render/gl"; import { FrameProfiler } from "./FrameProfiler"; @@ -261,6 +262,7 @@ export function createRenderer( const layers: Controller[] = [ new WarshipSelectionController(game, eventBus, transformHandler, view), new BuildPreviewController(game, eventBus, uiState, transformHandler, view), + new HoverHighlightController(game, eventBus, transformHandler, view), new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings), eventsDisplay, attacksDisplay, diff --git a/src/client/render/gl/game-view.ts b/src/client/render/gl/game-view.ts index 48d7f0306a..21562ce812 100644 --- a/src/client/render/gl/game-view.ts +++ b/src/client/render/gl/game-view.ts @@ -29,8 +29,6 @@ import type { GameViewEventType, RadialMenuItem, } from "./events"; -import type { MapKeyBindings } from "./map-interaction"; -import { MapInteraction } from "./map-interaction"; import type { SpawnCenter } from "./passes/spawn-overlay-pass"; import type { RenderSettings } from "./render-settings"; import { GPURenderer } from "./renderer"; @@ -38,7 +36,6 @@ import { GPURenderer } from "./renderer"; export class GameView { private renderer: GPURenderer; private resizeObs: ResizeObserver | null = null; - private interaction: MapInteraction; private listeners = new Map void>>(); @@ -49,7 +46,6 @@ export class GameView { paletteData: Float32Array, raf?: typeof requestAnimationFrame, caf?: typeof cancelAnimationFrame, - keyBindings?: MapKeyBindings, ) { this.renderer = new GPURenderer( canvas, @@ -60,44 +56,6 @@ export class GameView { caf, ); - // Create interaction handler and wire DOM events - this.interaction = new MapInteraction({ - renderer: this.renderer, - emit: this.emit.bind(this), - raf: raf ?? requestAnimationFrame.bind(window), - caf: caf ?? cancelAnimationFrame.bind(window), - keyBindings, - }); - - canvas.addEventListener("pointerdown", (e) => - this.interaction.handlePointerDown(e), - ); - canvas.addEventListener("pointermove", (e) => - this.interaction.handlePointerMove(e), - ); - canvas.addEventListener("pointerup", (e) => - this.interaction.handlePointerUp(e), - ); - canvas.addEventListener("pointercancel", (e) => - this.interaction.handlePointerUp(e), - ); - canvas.addEventListener("wheel", (e) => this.interaction.handleWheel(e), { - passive: false, - }); - canvas.addEventListener("contextmenu", (e) => - this.interaction.handleContextMenu(e), - ); - canvas.addEventListener("dblclick", (e) => - this.interaction.handleDblClick(e), - ); - canvas.addEventListener("auxclick", (e) => - this.interaction.handleAuxClick(e), - ); - document.addEventListener("keydown", (e) => - this.interaction.handleKeyDown(e), - ); - document.addEventListener("keyup", (e) => this.interaction.handleKeyUp(e)); - this.resizeObs = new ResizeObserver((entries) => { for (const entry of entries) { const { width, height } = entry.contentRect; @@ -110,18 +68,6 @@ export class GameView { if (rect.width > 0) this.renderer.resize(rect.width, rect.height); } - /** - * Forward a pointermove event into the MapInteraction handler. The WebGL - * canvas itself has pointer-events: none (input flows through a separate - * overlay div in the main client), so the listener bound to `canvas` in - * the constructor never actually fires for game-mode input. Callers that - * own the active input element forward pointermove events here so hover - * tracking + setHighlightOwner still work. - */ - handlePointerMove(e: PointerEvent): void { - this.interaction.handlePointerMove(e); - } - // ---- Event system ---- on( @@ -161,25 +107,18 @@ export class GameView { centerItem?: RadialMenuItem, ): void { this.renderer.showRadialMenu(screenX, screenY, items, centerItem); - // Cursor is at anchor — center starts hovered (synced with RadialMenuPass) - this.interaction.setMenuHoveredSeg( - this.renderer.radialMenuHitTest(screenX, screenY), - ); } hideRadialMenu(): void { this.renderer.hideRadialMenu(); - this.interaction.setMenuHoveredSeg(-1); } openRadialSubMenu(subItems: RadialMenuItem[]): void { this.renderer.openRadialSubMenu(subItems); - this.interaction.setMenuHoveredSeg(-1); } goBackRadialMenu(): void { this.renderer.goBackRadialMenu(); - this.interaction.setMenuHoveredSeg(-1); } get radialMenuVisible(): boolean { @@ -325,7 +264,6 @@ export class GameView { /** Update ghost structure preview (build-mode visualization). null = clear. */ updateGhostPreview(data: GhostPreviewData | null): void { - this.interaction.setHasGhostPreview(data !== null); this.renderer.updateGhostPreview(data); } @@ -380,21 +318,8 @@ export class GameView { // ---- Other ---- - setFitZoomOnDoubleClick(v: boolean): void { - this.interaction.fitZoomOnDoubleClick = v; - } - setDefaultGridView(v: boolean): void { - this.interaction.setDefaultGridView(v); - } setLocalPlayerID(id: number): void { this.renderer.setLocalPlayerID(id); - this.interaction.setLocalPlayerID(id); - } - setPanSpeed(speed: number): void { - this.interaction.setPanSpeed(speed); - } - setZoomSpeed(speed: number): void { - this.interaction.setZoomSpeed(speed); } setHighlightOwner(ownerID: number): void { this.renderer.setHighlightOwner(ownerID); @@ -418,7 +343,6 @@ export class GameView { // ---- Lifecycle ---- dispose(): void { - this.interaction.dispose(); this.resizeObs?.disconnect(); this.resizeObs = null; this.listeners.clear(); diff --git a/src/client/render/gl/index.ts b/src/client/render/gl/index.ts index 93e4ee05a0..2b0942f038 100644 --- a/src/client/render/gl/index.ts +++ b/src/client/render/gl/index.ts @@ -9,8 +9,6 @@ export type { RadialMenuSelectEvent, } from "./events"; export { GameView } from "./game-view"; -export { REPLAY_KEY_BINDINGS } from "./map-interaction"; -export type { MapKeyBindings } from "./map-interaction"; export type { SpawnCenter } from "./passes/spawn-overlay-pass"; export { createRenderSettings, dumpSettings } from "./render-settings"; export type { RenderSettings } from "./render-settings"; diff --git a/src/client/render/gl/keyboard-pan.ts b/src/client/render/gl/keyboard-pan.ts deleted file mode 100644 index 797e9e0f56..0000000000 --- a/src/client/render/gl/keyboard-pan.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * KeyboardPan — WASD camera panning, Q/E smooth zoom, and C fit-zoom. - * - * Tracks held keys and runs a requestAnimationFrame loop while any - * direction or zoom key is pressed. All movement is frame-rate - * independent. Pan speed is zoom-adaptive (faster when zoomed out). - * - * Skips all input when the user is typing in an input/textarea. - */ - -const WASD = new Set(["w", "a", "s", "d"]); -const ZOOM_KEYS = new Set(["q", "e"]); -const DEFAULT_PAN_SPEED = 800; // tiles per second at zoom = 1 -const DEFAULT_ZOOM_SPEED = 2.0; // zoom multiplier per second (e.g. 2× per second held) - -interface KeyboardPanDeps { - panBy(dx: number, dy: number): void; - zoomBy(factor: number): void; - focusOwner(ownerID: number): void; - fitMap(): void; - readonly zoom: number; -} - -export class KeyboardPan { - private deps: KeyboardPanDeps; - private raf: typeof requestAnimationFrame; - private caf: typeof cancelAnimationFrame; - - private held = new Set(); - private animId: number | null = null; - private lastTime = 0; - private localPlayerID = 0; - private panSpeed = DEFAULT_PAN_SPEED; - private zoomSpeed = DEFAULT_ZOOM_SPEED; - - constructor( - deps: KeyboardPanDeps, - raf: typeof requestAnimationFrame = requestAnimationFrame.bind(window), - caf: typeof cancelAnimationFrame = cancelAnimationFrame.bind(window), - ) { - this.deps = deps; - this.raf = raf; - this.caf = caf; - } - - handleKeyDown(e: KeyboardEvent): boolean { - if (isTyping()) return false; - - const key = e.key.toLowerCase(); - - if (key === "c" && !e.repeat) { - if (this.localPlayerID > 0) this.deps.focusOwner(this.localPlayerID); - else this.deps.fitMap(); - return true; - } - - if (WASD.has(key) || ZOOM_KEYS.has(key)) { - this.held.add(key); - if (this.animId === null) this.startLoop(); - return true; - } - - return false; - } - - handleKeyUp(e: KeyboardEvent): boolean { - const key = e.key.toLowerCase(); - if (WASD.has(key) || ZOOM_KEYS.has(key)) { - this.held.delete(key); - if (this.held.size === 0) this.stopLoop(); - return true; - } - return false; - } - - setLocalPlayerID(id: number): void { - this.localPlayerID = id; - } - setPanSpeed(speed: number): void { - this.panSpeed = speed; - } - setZoomSpeed(speed: number): void { - this.zoomSpeed = speed; - } - - dispose(): void { - this.stopLoop(); - this.held.clear(); - } - - // ---- Animation loop ---- - - private startLoop(): void { - this.lastTime = performance.now(); - this.animId = this.raf(this.loop); - } - - private stopLoop(): void { - if (this.animId !== null) { - this.caf(this.animId); - this.animId = null; - } - } - - private loop = (): void => { - const now = performance.now(); - const dt = Math.min((now - this.lastTime) / 1000, 0.1); // cap at 100ms - this.lastTime = now; - - const speed = this.panSpeed / this.deps.zoom; - let dx = 0; - let dy = 0; - if (this.held.has("a")) dx -= speed * dt; - if (this.held.has("d")) dx += speed * dt; - if (this.held.has("w")) dy -= speed * dt; - if (this.held.has("s")) dy += speed * dt; - - if (dx !== 0 || dy !== 0) this.deps.panBy(dx, dy); - - // Q/E smooth zoom: compute multiplicative factor for this frame - let zoomDir = 0; - if (this.held.has("e")) zoomDir += 1; - if (this.held.has("q")) zoomDir -= 1; - if (zoomDir !== 0) { - const factor = this.zoomSpeed ** (zoomDir * dt); - this.deps.zoomBy(factor); - } - - if (this.held.size > 0) this.animId = this.raf(this.loop); - else this.animId = null; - }; -} - -function isTyping(): boolean { - const el = document.activeElement; - if (!el) return false; - const tag = el.tagName; - if (tag === "INPUT" || tag === "TEXTAREA") return true; - if ((el as HTMLElement).isContentEditable) return true; - return false; -} diff --git a/src/client/render/gl/map-interaction.ts b/src/client/render/gl/map-interaction.ts deleted file mode 100644 index 4e353206e5..0000000000 --- a/src/client/render/gl/map-interaction.ts +++ /dev/null @@ -1,418 +0,0 @@ -/** - * MapInteraction — handles all DOM pointer and keyboard events for GameView. - * - * Owns: - * - Drag state: dragging, lastX/Y, downX/Y - * - Menu hover state: menuHoveredSeg - * - Timing guards: lastMenuDismissAt, lastGhostClickAt - * - Ghost preview flag: hasGhostPreview - * - Alt-view flag: altView (affiliation recoloring, configurable hold key) - * - Grid-view flag: gridView (coordinate grid, configurable toggle key) - * - Hover tracking: lastHoverOwner, lastHoverUnitId, lastHoverStructureId, lastHoverTileX/Y - * - * All handler methods (pointerdown, pointermove, pointerup, keydown, keyup, wheel, contextmenu, auxclick, dblclick) - * are defined here and bound by GameView. - */ - -import type { - GameViewEventMap, - GameViewEventType, - MapPointerEvent, -} from "./events"; -import { KeyboardPan } from "./keyboard-pan"; -import type { GPURenderer } from "./renderer"; - -const HIT_RADIUS_PX = 16; -const CLICK_THRESHOLD_SQ = 100; - -/** Describes a hold-key binding (key held = active, released = inactive). */ -export interface HoldKeyBinding { - /** KeyboardEvent.code to match (e.g. "Space", "KeyM"). */ - code: string; - /** Require shift modifier. Default false. */ - shift?: boolean; -} - -/** Describes a toggle-key binding (each press toggles). */ -export interface ToggleKeyBinding { - /** KeyboardEvent.key to match (e.g. "m", "g"). */ - key: string; -} - -/** Configurable keybindings for MapInteraction. */ -export interface MapKeyBindings { - /** Hold to peek alt-view (affiliation recoloring) + grid. */ - altViewPeek: HoldKeyBinding; - /** Toggle grid overlay on/off. */ - gridToggle: ToggleKeyBinding; -} - -/** Extension default: Space hold for altView peek, 'm' toggle for grid. */ -export const DEFAULT_KEY_BINDINGS: MapKeyBindings = { - altViewPeek: { code: "Space" }, - gridToggle: { key: "m" }, -}; - -/** Replay default: Shift+M hold for altView peek, 'm' toggle for grid. */ -export const REPLAY_KEY_BINDINGS: MapKeyBindings = { - altViewPeek: { code: "KeyG", shift: true }, - gridToggle: { key: "g" }, -}; - -interface InteractionDeps { - renderer: GPURenderer; - emit: ( - event: K, - payload: GameViewEventMap[K], - ) => void; - raf: typeof requestAnimationFrame; - caf: typeof cancelAnimationFrame; - keyBindings?: MapKeyBindings; -} - -export class MapInteraction { - private deps: InteractionDeps; - private keys: MapKeyBindings; - - // Drag state - private dragging = false; - private lastX = 0; - private lastY = 0; - private downX = 0; - private downY = 0; - - // Hover tracking - private lastHoverOwner = 0; - private lastHoverUnitId: number | null = null; - private lastHoverStructureId: number | null = null; - private lastHoverTileX = -1; - private lastHoverTileY = -1; - - // Timing guards - private hasGhostPreview = false; - private lastGhostClickAt = 0; - private lastMenuDismissAt = 0; - - // Menu hover - private menuHoveredSeg = -1; - - // Grid-view: coordinate grid overlay. Toggled by configured key, persisted. - private gridViewBase = false; - private gridView = false; - - // Alt-view: affiliation recoloring (no persistent toggle). - private altView = false; - - // Alt-view peek hold state. - private peekHeld = false; - - // Interaction settings (mutable — updated live by extension) - fitZoomOnDoubleClick = true; - - // Keyboard camera control (WASD pan + C fit-zoom) - private keyboardPan: KeyboardPan; - - constructor(deps: InteractionDeps) { - this.deps = deps; - this.keys = deps.keyBindings ?? DEFAULT_KEY_BINDINGS; - this.keyboardPan = new KeyboardPan(deps.renderer, deps.raf, deps.caf); - } - - // ---- Pointer event handlers ---- - - handlePointerDown(e: PointerEvent): void { - if (e.button !== 0) return; - - // If radial menu is open, clicking outside dismisses it - if (this.deps.renderer.radialMenuVisible) { - const hit = this.deps.renderer.radialMenuHitTest(e.clientX, e.clientY); - if (hit === -1) { - this.deps.renderer.hideRadialMenu(); - this.menuHoveredSeg = -1; - this.lastMenuDismissAt = performance.now(); - } - return; // consume the event either way — don't start dragging - } - - if (this.hasGhostPreview) this.lastGhostClickAt = performance.now(); - this.dragging = true; - this.lastX = e.clientX; - this.lastY = e.clientY; - this.downX = e.clientX; - this.downY = e.clientY; - (e.target as HTMLElement).setPointerCapture(e.pointerId); - } - - handlePointerMove(e: PointerEvent): void { - // Update radial menu hover - if (this.deps.renderer.radialMenuVisible) { - const hit = this.deps.renderer.radialMenuHitTest(e.clientX, e.clientY); - if (hit !== this.menuHoveredSeg) { - this.menuHoveredSeg = hit; - this.deps.renderer.setRadialMenuHover(hit); - } - return; // don't pan or update game hover while menu is open - } - - if (this.dragging) { - const dx = e.clientX - this.lastX; - const dy = e.clientY - this.lastY; - this.lastX = e.clientX; - this.lastY = e.clientY; - const dpr = window.devicePixelRatio || 1; - this.deps.renderer.panBy( - -(dx * dpr) / this.deps.renderer.zoom, - -(dy * dpr) / this.deps.renderer.zoom, - ); - return; - } - this.updateHover(e); - } - - handlePointerUp(e: PointerEvent): void { - if (e.button !== 0) return; - - // If radial menu is open, a click on a segment or center selects it. - // Don't hide the menu here — the menuselect handler decides whether to - // close or open a submenu. - if (this.deps.renderer.radialMenuVisible) { - if (this.menuHoveredSeg !== -1) { - const item = this.deps.renderer.getRadialMenuItemAt( - this.menuHoveredSeg, - ); - if (item && item.enabled) { - this.deps.emit("menuselect", { - index: this.menuHoveredSeg, - id: item.id, - }); - } - if (!this.deps.renderer.radialMenuVisible) { - this.lastMenuDismissAt = performance.now(); - } - this.menuHoveredSeg = -1; - } - return; - } - - if (!this.dragging) return; - this.dragging = false; - (e.target as HTMLElement).releasePointerCapture(e.pointerId); - const dx = e.clientX - this.downX; - const dy = e.clientY - this.downY; - if (dx * dx + dy * dy < CLICK_THRESHOLD_SQ) { - this.deps.emit("click", this.buildEvent(e, 0)); - } - } - - // ---- Keyboard event handlers ---- - - handleKeyDown(e: KeyboardEvent): void { - if (e.key === "Escape" && this.deps.renderer.radialMenuVisible) { - this.deps.renderer.hideRadialMenu(); - this.menuHoveredSeg = -1; - this.lastMenuDismissAt = performance.now(); - } - if ( - this.matchesHold(e, this.keys.altViewPeek) && - !e.repeat && - !this.peekHeld - ) { - e.preventDefault(); - this.peekHeld = true; - this.applyAltView(true); - this.applyGridView(true); - this.deps.emit("altviewpeek", { active: true }); - } - if (e.key === this.keys.gridToggle.key && !e.shiftKey && !e.repeat) { - this.gridViewBase = !this.gridViewBase; - this.applyGridView(this.gridViewBase); - this.deps.emit("gridviewtoggle", { active: this.gridViewBase }); - } - this.keyboardPan.handleKeyDown(e); - } - - handleKeyUp(e: KeyboardEvent): void { - if (e.code === this.keys.altViewPeek.code && this.peekHeld) { - e.preventDefault(); - this.peekHeld = false; - this.applyAltView(false); - this.applyGridView(this.gridViewBase); - this.deps.emit("altviewpeek", { active: false }); - } - this.keyboardPan.handleKeyUp(e); - } - - private matchesHold(e: KeyboardEvent, binding: HoldKeyBinding): boolean { - return e.code === binding.code && (!binding.shift || e.shiftKey); - } - - // ---- Other event handlers ---- - - handleWheel(e: WheelEvent): void { - e.preventDefault(); - if (e.shiftKey || e.ctrlKey || e.altKey) { - this.deps.emit("scroll", { - deltaX: e.deltaX, - deltaY: e.deltaY, - shiftKey: e.shiftKey, - ctrlKey: e.ctrlKey, - altKey: e.altKey, - }); - return; - } - const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1; - this.deps.renderer.zoomAtScreen(factor, e.clientX, e.clientY); - } - - handleContextMenu(e: MouseEvent): void { - e.preventDefault(); - // Dismiss any open menu first — the external manager will decide whether to reopen - if (this.deps.renderer.radialMenuVisible) { - this.deps.renderer.hideRadialMenu(); - this.menuHoveredSeg = -1; - this.lastMenuDismissAt = performance.now(); - } - this.deps.emit("contextmenu", this.buildEvent(e, 2)); - } - - handleAuxClick(e: MouseEvent): void { - if (e.button !== 1) return; - e.preventDefault(); - this.deps.emit("middleclick", this.buildEvent(e, 1)); - } - - handleDblClick(e: MouseEvent): void { - // Suppress fitzoom if menu is open or was recently open - if (this.deps.renderer.radialMenuVisible) return; - const now = performance.now(); - if (now - this.lastMenuDismissAt < 500) return; - - const evt = this.buildEvent(e, 0); - if (this.fitZoomOnDoubleClick && now - this.lastGhostClickAt > 500) { - if (evt.ownerID !== 0) this.deps.renderer.focusOwner(evt.ownerID); - else this.deps.renderer.fitMap(); - } - this.deps.emit("dblclick", evt); - } - - // ---- Hover tracking ---- - - private updateHover(e: PointerEvent): void { - const world = this.deps.renderer.screenToWorld(e.clientX, e.clientY); - const tileX = Math.floor(world.x); - const tileY = Math.floor(world.y); - const ownerID = this.deps.renderer.getOwnerAtWorld(world.x, world.y); - const hitRadius = HIT_RADIUS_PX / this.deps.renderer.zoom; - const unit = this.deps.renderer.getUnitAtWorld(world.x, world.y, hitRadius); - const structure = this.deps.renderer.getStructureAtWorld( - world.x, - world.y, - hitRadius, - ); - const unitId = unit?.id ?? null; - const structureId = structure?.id ?? null; - - if ( - ownerID !== this.lastHoverOwner || - unitId !== this.lastHoverUnitId || - structureId !== this.lastHoverStructureId || - tileX !== this.lastHoverTileX || - tileY !== this.lastHoverTileY - ) { - this.lastHoverOwner = ownerID; - this.lastHoverUnitId = unitId; - this.lastHoverStructureId = structureId; - this.lastHoverTileX = tileX; - this.lastHoverTileY = tileY; - this.deps.renderer.setHighlightOwner(ownerID); - this.deps.emit("hover", { - screenX: e.clientX, - screenY: e.clientY, - worldX: world.x, - worldY: world.y, - tileX, - tileY, - ownerID, - unit, - structure, - button: 0, - shiftKey: e.shiftKey, - ctrlKey: e.ctrlKey || e.metaKey, - altKey: e.altKey, - }); - } - } - - private buildEvent(e: MouseEvent, button: number): MapPointerEvent { - const world = this.deps.renderer.screenToWorld(e.clientX, e.clientY); - const hitRadius = HIT_RADIUS_PX / this.deps.renderer.zoom; - return { - screenX: e.clientX, - screenY: e.clientY, - worldX: world.x, - worldY: world.y, - tileX: Math.floor(world.x), - tileY: Math.floor(world.y), - ownerID: this.deps.renderer.getOwnerAtWorld(world.x, world.y), - unit: this.deps.renderer.getUnitAtWorld(world.x, world.y, hitRadius), - structure: this.deps.renderer.getStructureAtWorld( - world.x, - world.y, - hitRadius, - ), - button, - shiftKey: e.shiftKey, - ctrlKey: e.ctrlKey || e.metaKey, - altKey: e.altKey, - }; - } - - // ---- View helpers ---- - - private applyAltView(active: boolean): void { - if (active === this.altView) return; - this.altView = active; - this.deps.renderer.setAltView(active); - } - - private applyGridView(active: boolean): void { - if (active === this.gridView) return; - this.gridView = active; - this.deps.renderer.setGridView(active); - } - - // ---- Public API ---- - - setDefaultGridView(v: boolean): void { - this.gridViewBase = v; - if (!this.peekHeld) this.applyGridView(v); - } - - setHasGhostPreview(v: boolean): void { - this.hasGhostPreview = v; - } - - getMenuHoveredSeg(): number { - return this.menuHoveredSeg; - } - - setMenuHoveredSeg(v: number): void { - this.menuHoveredSeg = v; - } - - setLocalPlayerID(id: number): void { - this.keyboardPan.setLocalPlayerID(id); - } - - setPanSpeed(speed: number): void { - this.keyboardPan.setPanSpeed(speed); - } - - setZoomSpeed(speed: number): void { - this.keyboardPan.setZoomSpeed(speed); - } - - dispose(): void { - this.keyboardPan.dispose(); - } -} From 4cd22a9b5cc20fe833a894a907e519509a07cbbc Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sun, 17 May 2026 21:21:05 -0700 Subject: [PATCH 32/34] rename render/ files to UpperCamelCase to match client convention The render/ tree was the only place in the client still using kebab-case filenames. Brings ~80 files in line with the rest of src/client/ (BuildPreviewController, TransformHandler, etc.). Directories kept as they were (name-pass/, fx-pass/, passes/, utils/, debug/) since the codebase already mixes those. Two collisions surfaced and got resolved: render/types/ is a directory, not a file, so its imports kept the lowercase form; and the sed pass incidentally normalized core/pathfinding imports, which had to be reverted since that file is actually lowercase on disk despite some imports having referenced it as ./Types under macOS case-insensitive resolution. --- src/client/WebGLFrameBuilder.ts | 2 +- .../controllers/BuildPreviewController.ts | 2 +- .../controllers/HoverHighlightController.ts | 2 +- .../{game-constants.ts => GameConstants.ts} | 0 .../{railroad-cache.ts => RailroadCache.ts} | 0 .../{trail-manager.ts => TrailManager.ts} | 0 .../render/frame/{upload.ts => Upload.ts} | 0 ...liance-clusters.ts => AllianceClusters.ts} | 0 .../{attack-rings.ts => AttackRings.ts} | 0 .../{nuke-telegraphs.ts => NukeTelegraphs.ts} | 0 .../{player-status.ts => PlayerStatus.ts} | 0 .../{relation-matrix.ts => RelationMatrix.ts} | 0 src/client/render/frame/index.ts | 16 ++--- src/client/render/gl/{camera.ts => Camera.ts} | 0 .../{dynamic-buffer.ts => DynamicBuffer.ts} | 0 src/client/render/gl/{events.ts => Events.ts} | 0 .../render/gl/{game-view.ts => GameView.ts} | 8 +-- .../{render-settings.ts => RenderSettings.ts} | 0 .../render/gl/{renderer.ts => Renderer.ts} | 72 +++++++++---------- .../{settings-utils.ts => SettingsUtils.ts} | 0 .../debug/{config-prop.ts => ConfigProp.ts} | 0 .../render/gl/debug/{folder.ts => Folder.ts} | 2 +- .../render/gl/debug/{layout.ts => Layout.ts} | 14 ++-- .../render/gl/debug/{tree.ts => Tree.ts} | 4 +- .../render/gl/debug/{wiring.ts => Wiring.ts} | 8 +-- src/client/render/gl/debug/index.ts | 10 +-- .../gl/debug/props/{color.ts => Color.ts} | 2 +- .../gl/debug/props/{select.ts => Select.ts} | 2 +- .../gl/debug/props/{slider.ts => Slider.ts} | 2 +- .../gl/debug/props/{toggle.ts => Toggle.ts} | 2 +- src/client/render/gl/index.ts | 18 ++--- .../gl/passes/{bar-pass.ts => BarPass.ts} | 6 +- ...r-compute-pass.ts => BorderComputePass.ts} | 6 +- ...order-stamp-pass.ts => BorderStampPass.ts} | 8 +-- ...est-popup-pass.ts => ConquestPopupPass.ts} | 14 ++-- ...ate-grid-pass.ts => CoordinateGridPass.ts} | 4 +- .../{crosshair-pass.ts => CrosshairPass.ts} | 2 +- ...lout-bloom-pass.ts => FalloutBloomPass.ts} | 8 +-- ...lout-light-pass.ts => FalloutLightPass.ts} | 8 +-- .../{lightmap-pass.ts => LightmapPass.ts} | 8 +-- ...indicator-pass.ts => MoveIndicatorPass.ts} | 4 +- ...omposite-pass.ts => NightCompositePass.ts} | 4 +- ...telegraph-pass.ts => NukeTelegraphPass.ts} | 6 +- ...ajectory-pass.ts => NukeTrajectoryPass.ts} | 4 +- ...{point-light-pass.ts => PointLightPass.ts} | 4 +- ...{radial-menu-pass.ts => RadialMenuPass.ts} | 4 +- .../{railroad-pass.ts => RailroadPass.ts} | 8 +-- ...ange-circle-pass.ts => RangeCirclePass.ts} | 2 +- .../{sam-radius-pass.ts => SamRadiusPass.ts} | 8 +-- ...ection-box-pass.ts => SelectionBoxPass.ts} | 2 +- ...wn-overlay-pass.ts => SpawnOverlayPass.ts} | 6 +- ...re-level-pass.ts => StructureLevelPass.ts} | 16 ++--- .../{structure-pass.ts => StructurePass.ts} | 8 +-- .../{terrain-pass.ts => TerrainPass.ts} | 2 +- .../{territory-pass.ts => TerritoryPass.ts} | 8 +-- .../gl/passes/{trail-pass.ts => TrailPass.ts} | 8 +-- .../gl/passes/{unit-pass.ts => UnitPass.ts} | 8 +-- ...ttack-ring-pass.ts => FxAttackRingPass.ts} | 6 +- ...x-shockwave-pass.ts => FxShockwavePass.ts} | 6 +- .../{fx-sprite-pass.ts => FxSpritePass.ts} | 8 +-- src/client/render/gl/passes/fx-pass/index.ts | 10 +-- .../name-pass/{atlas-data.ts => AtlasData.ts} | 4 +- .../{data-textures.ts => DataTextures.ts} | 6 +- .../{debug-program.ts => DebugProgram.ts} | 6 +- .../{icon-program.ts => IconProgram.ts} | 6 +- ...s-icon-program.ts => StatusIconProgram.ts} | 6 +- .../{text-layout.ts => TextLayout.ts} | 4 +- .../{text-program.ts => TextProgram.ts} | 8 +-- .../passes/name-pass/{types.ts => Types.ts} | 0 .../render/gl/passes/name-pass/index.ts | 24 +++---- .../utils/{affiliation.ts => Affiliation.ts} | 4 +- .../utils/{color-utils.ts => ColorUtils.ts} | 0 .../gl/utils/{gl-utils.ts => GlUtils.ts} | 0 .../{gpu-resources.ts => GpuResources.ts} | 2 +- .../utils/{heat-manager.ts => HeatManager.ts} | 6 +- .../{nuke-trajectory.ts => NukeTrajectory.ts} | 0 .../gl/utils/{tile-codec.ts => TileCodec.ts} | 0 .../types/{frame-data.ts => FrameData.ts} | 4 +- .../types/{frame-events.ts => FrameEvents.ts} | 2 +- .../types/{frame-source.ts => FrameSource.ts} | 4 +- src/client/render/types/{game.ts => Game.ts} | 0 .../types/{game-updates.ts => GameUpdates.ts} | 0 .../render/types/{renderer.ts => Renderer.ts} | 0 .../render/types/{replay.ts => Replay.ts} | 2 +- .../types/{unit-type.ts => UnitType.ts} | 0 src/client/render/types/index.ts | 22 +++--- src/client/view/GameView.ts | 14 ++-- .../render/frame/derive/player-status.test.ts | 2 +- 88 files changed, 244 insertions(+), 244 deletions(-) rename src/client/render/{game-constants.ts => GameConstants.ts} (100%) rename src/client/render/frame/{railroad-cache.ts => RailroadCache.ts} (100%) rename src/client/render/frame/{trail-manager.ts => TrailManager.ts} (100%) rename src/client/render/frame/{upload.ts => Upload.ts} (100%) rename src/client/render/frame/derive/{alliance-clusters.ts => AllianceClusters.ts} (100%) rename src/client/render/frame/derive/{attack-rings.ts => AttackRings.ts} (100%) rename src/client/render/frame/derive/{nuke-telegraphs.ts => NukeTelegraphs.ts} (100%) rename src/client/render/frame/derive/{player-status.ts => PlayerStatus.ts} (100%) rename src/client/render/frame/derive/{relation-matrix.ts => RelationMatrix.ts} (100%) rename src/client/render/gl/{camera.ts => Camera.ts} (100%) rename src/client/render/gl/{dynamic-buffer.ts => DynamicBuffer.ts} (100%) rename src/client/render/gl/{events.ts => Events.ts} (100%) rename src/client/render/gl/{game-view.ts => GameView.ts} (98%) rename src/client/render/gl/{render-settings.ts => RenderSettings.ts} (100%) rename src/client/render/gl/{renderer.ts => Renderer.ts} (94%) rename src/client/render/gl/{settings-utils.ts => SettingsUtils.ts} (100%) rename src/client/render/gl/debug/{config-prop.ts => ConfigProp.ts} (100%) rename src/client/render/gl/debug/{folder.ts => Folder.ts} (87%) rename src/client/render/gl/debug/{layout.ts => Layout.ts} (98%) rename src/client/render/gl/debug/{tree.ts => Tree.ts} (85%) rename src/client/render/gl/debug/{wiring.ts => Wiring.ts} (96%) rename src/client/render/gl/debug/props/{color.ts => Color.ts} (97%) rename src/client/render/gl/debug/props/{select.ts => Select.ts} (93%) rename src/client/render/gl/debug/props/{slider.ts => Slider.ts} (93%) rename src/client/render/gl/debug/props/{toggle.ts => Toggle.ts} (93%) rename src/client/render/gl/passes/{bar-pass.ts => BarPass.ts} (98%) rename src/client/render/gl/passes/{border-compute-pass.ts => BorderComputePass.ts} (98%) rename src/client/render/gl/passes/{border-stamp-pass.ts => BorderStampPass.ts} (96%) rename src/client/render/gl/passes/{conquest-popup-pass.ts => ConquestPopupPass.ts} (96%) rename src/client/render/gl/passes/{coordinate-grid-pass.ts => CoordinateGridPass.ts} (97%) rename src/client/render/gl/passes/{crosshair-pass.ts => CrosshairPass.ts} (98%) rename src/client/render/gl/passes/{fallout-bloom-pass.ts => FalloutBloomPass.ts} (98%) rename src/client/render/gl/passes/{fallout-light-pass.ts => FalloutLightPass.ts} (97%) rename src/client/render/gl/passes/{lightmap-pass.ts => LightmapPass.ts} (95%) rename src/client/render/gl/passes/{move-indicator-pass.ts => MoveIndicatorPass.ts} (97%) rename src/client/render/gl/passes/{night-composite-pass.ts => NightCompositePass.ts} (95%) rename src/client/render/gl/passes/{nuke-telegraph-pass.ts => NukeTelegraphPass.ts} (96%) rename src/client/render/gl/passes/{nuke-trajectory-pass.ts => NukeTrajectoryPass.ts} (99%) rename src/client/render/gl/passes/{point-light-pass.ts => PointLightPass.ts} (98%) rename src/client/render/gl/passes/{radial-menu-pass.ts => RadialMenuPass.ts} (99%) rename src/client/render/gl/passes/{railroad-pass.ts => RailroadPass.ts} (98%) rename src/client/render/gl/passes/{range-circle-pass.ts => RangeCirclePass.ts} (97%) rename src/client/render/gl/passes/{sam-radius-pass.ts => SamRadiusPass.ts} (98%) rename src/client/render/gl/passes/{selection-box-pass.ts => SelectionBoxPass.ts} (98%) rename src/client/render/gl/passes/{spawn-overlay-pass.ts => SpawnOverlayPass.ts} (97%) rename src/client/render/gl/passes/{structure-level-pass.ts => StructureLevelPass.ts} (95%) rename src/client/render/gl/passes/{structure-pass.ts => StructurePass.ts} (98%) rename src/client/render/gl/passes/{terrain-pass.ts => TerrainPass.ts} (98%) rename src/client/render/gl/passes/{territory-pass.ts => TerritoryPass.ts} (98%) rename src/client/render/gl/passes/{trail-pass.ts => TrailPass.ts} (94%) rename src/client/render/gl/passes/{unit-pass.ts => UnitPass.ts} (98%) rename src/client/render/gl/passes/fx-pass/{fx-attack-ring-pass.ts => FxAttackRingPass.ts} (97%) rename src/client/render/gl/passes/fx-pass/{fx-shockwave-pass.ts => FxShockwavePass.ts} (96%) rename src/client/render/gl/passes/fx-pass/{fx-sprite-pass.ts => FxSpritePass.ts} (98%) rename src/client/render/gl/passes/name-pass/{atlas-data.ts => AtlasData.ts} (96%) rename src/client/render/gl/passes/name-pass/{data-textures.ts => DataTextures.ts} (93%) rename src/client/render/gl/passes/name-pass/{debug-program.ts => DebugProgram.ts} (94%) rename src/client/render/gl/passes/name-pass/{icon-program.ts => IconProgram.ts} (97%) rename src/client/render/gl/passes/name-pass/{status-icon-program.ts => StatusIconProgram.ts} (97%) rename src/client/render/gl/passes/name-pass/{text-layout.ts => TextLayout.ts} (95%) rename src/client/render/gl/passes/name-pass/{text-program.ts => TextProgram.ts} (96%) rename src/client/render/gl/passes/name-pass/{types.ts => Types.ts} (100%) rename src/client/render/gl/utils/{affiliation.ts => Affiliation.ts} (97%) rename src/client/render/gl/utils/{color-utils.ts => ColorUtils.ts} (100%) rename src/client/render/gl/utils/{gl-utils.ts => GlUtils.ts} (100%) rename src/client/render/gl/utils/{gpu-resources.ts => GpuResources.ts} (97%) rename src/client/render/gl/utils/{heat-manager.ts => HeatManager.ts} (98%) rename src/client/render/gl/utils/{nuke-trajectory.ts => NukeTrajectory.ts} (100%) rename src/client/render/gl/utils/{tile-codec.ts => TileCodec.ts} (100%) rename src/client/render/types/{frame-data.ts => FrameData.ts} (97%) rename src/client/render/types/{frame-events.ts => FrameEvents.ts} (99%) rename src/client/render/types/{frame-source.ts => FrameSource.ts} (92%) rename src/client/render/types/{game.ts => Game.ts} (100%) rename src/client/render/types/{game-updates.ts => GameUpdates.ts} (100%) rename src/client/render/types/{renderer.ts => Renderer.ts} (100%) rename src/client/render/types/{replay.ts => Replay.ts} (99%) rename src/client/render/types/{unit-type.ts => UnitType.ts} (100%) diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index b98228ef29..065fc4ebba 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -1,6 +1,6 @@ import { Colord } from "colord"; import { GameView } from "../core/game/GameView"; -import { uploadFrameData } from "./render/frame/upload"; +import { uploadFrameData } from "./render/frame/Upload"; import { PlayerStatic, GameView as WebGLGameView } from "./render/gl"; const PALETTE_SIZE = 4096; diff --git a/src/client/controllers/BuildPreviewController.ts b/src/client/controllers/BuildPreviewController.ts index f4de2d842c..661a65805b 100644 --- a/src/client/controllers/BuildPreviewController.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -23,7 +23,7 @@ import { MouseUpEvent, } from "../InputHandler"; import { GameView as WebGLGameView, buildNukeTrajectory } from "../render/gl"; -import type { SAMInfo } from "../render/gl/utils/nuke-trajectory"; +import type { SAMInfo } from "../render/gl/utils/NukeTrajectory"; import type { GhostPreviewData } from "../render/types"; import { TransformHandler } from "../TransformHandler"; import { diff --git a/src/client/controllers/HoverHighlightController.ts b/src/client/controllers/HoverHighlightController.ts index 9a0e35aa2e..bf1f69552c 100644 --- a/src/client/controllers/HoverHighlightController.ts +++ b/src/client/controllers/HoverHighlightController.ts @@ -13,7 +13,7 @@ import { GameView } from "../../core/game/GameView"; import { Controller } from "../Controller"; import { MouseMoveEvent } from "../InputHandler"; import { GameView as WebGLGameView } from "../render/gl"; -import { OWNER_MASK } from "../render/gl/utils/tile-codec"; +import { OWNER_MASK } from "../render/gl/utils/TileCodec"; import { TransformHandler } from "../TransformHandler"; export class HoverHighlightController implements Controller { diff --git a/src/client/render/game-constants.ts b/src/client/render/GameConstants.ts similarity index 100% rename from src/client/render/game-constants.ts rename to src/client/render/GameConstants.ts diff --git a/src/client/render/frame/railroad-cache.ts b/src/client/render/frame/RailroadCache.ts similarity index 100% rename from src/client/render/frame/railroad-cache.ts rename to src/client/render/frame/RailroadCache.ts diff --git a/src/client/render/frame/trail-manager.ts b/src/client/render/frame/TrailManager.ts similarity index 100% rename from src/client/render/frame/trail-manager.ts rename to src/client/render/frame/TrailManager.ts diff --git a/src/client/render/frame/upload.ts b/src/client/render/frame/Upload.ts similarity index 100% rename from src/client/render/frame/upload.ts rename to src/client/render/frame/Upload.ts diff --git a/src/client/render/frame/derive/alliance-clusters.ts b/src/client/render/frame/derive/AllianceClusters.ts similarity index 100% rename from src/client/render/frame/derive/alliance-clusters.ts rename to src/client/render/frame/derive/AllianceClusters.ts diff --git a/src/client/render/frame/derive/attack-rings.ts b/src/client/render/frame/derive/AttackRings.ts similarity index 100% rename from src/client/render/frame/derive/attack-rings.ts rename to src/client/render/frame/derive/AttackRings.ts diff --git a/src/client/render/frame/derive/nuke-telegraphs.ts b/src/client/render/frame/derive/NukeTelegraphs.ts similarity index 100% rename from src/client/render/frame/derive/nuke-telegraphs.ts rename to src/client/render/frame/derive/NukeTelegraphs.ts diff --git a/src/client/render/frame/derive/player-status.ts b/src/client/render/frame/derive/PlayerStatus.ts similarity index 100% rename from src/client/render/frame/derive/player-status.ts rename to src/client/render/frame/derive/PlayerStatus.ts diff --git a/src/client/render/frame/derive/relation-matrix.ts b/src/client/render/frame/derive/RelationMatrix.ts similarity index 100% rename from src/client/render/frame/derive/relation-matrix.ts rename to src/client/render/frame/derive/RelationMatrix.ts diff --git a/src/client/render/frame/index.ts b/src/client/render/frame/index.ts index fcac131809..ddcb4fa55b 100644 --- a/src/client/render/frame/index.ts +++ b/src/client/render/frame/index.ts @@ -2,19 +2,19 @@ export type { FrameData } from "../types"; // Shared derive functions -export { computeAllianceClusters } from "./derive/alliance-clusters"; +export { computeAllianceClusters } from "./derive/AllianceClusters"; export { extractAttackRings, extractAttackRingsFromIds, -} from "./derive/attack-rings"; +} from "./derive/AttackRings"; export { extractNukeTelegraphs, extractNukeTelegraphsFromIds, -} from "./derive/nuke-telegraphs"; -export { computePlayerStatus } from "./derive/player-status"; -export { buildRelationMatrix, buildTeamMap } from "./derive/relation-matrix"; +} from "./derive/NukeTelegraphs"; +export { computePlayerStatus } from "./derive/PlayerStatus"; +export { buildRelationMatrix, buildTeamMap } from "./derive/RelationMatrix"; // Upload -export type { RelationMatrixResult } from "./derive/relation-matrix"; -export { uploadFrameData } from "./upload"; -export type { FrameUploadTarget, UploadOptions } from "./upload"; +export type { RelationMatrixResult } from "./derive/RelationMatrix"; +export { uploadFrameData } from "./Upload"; +export type { FrameUploadTarget, UploadOptions } from "./Upload"; diff --git a/src/client/render/gl/camera.ts b/src/client/render/gl/Camera.ts similarity index 100% rename from src/client/render/gl/camera.ts rename to src/client/render/gl/Camera.ts diff --git a/src/client/render/gl/dynamic-buffer.ts b/src/client/render/gl/DynamicBuffer.ts similarity index 100% rename from src/client/render/gl/dynamic-buffer.ts rename to src/client/render/gl/DynamicBuffer.ts diff --git a/src/client/render/gl/events.ts b/src/client/render/gl/Events.ts similarity index 100% rename from src/client/render/gl/events.ts rename to src/client/render/gl/Events.ts diff --git a/src/client/render/gl/game-view.ts b/src/client/render/gl/GameView.ts similarity index 98% rename from src/client/render/gl/game-view.ts rename to src/client/render/gl/GameView.ts index 21562ce812..d0f68b4faa 100644 --- a/src/client/render/gl/game-view.ts +++ b/src/client/render/gl/GameView.ts @@ -28,10 +28,10 @@ import type { GameViewEventMap, GameViewEventType, RadialMenuItem, -} from "./events"; -import type { SpawnCenter } from "./passes/spawn-overlay-pass"; -import type { RenderSettings } from "./render-settings"; -import { GPURenderer } from "./renderer"; +} from "./Events"; +import type { SpawnCenter } from "./passes/SpawnOverlayPass"; +import { GPURenderer } from "./Renderer"; +import type { RenderSettings } from "./RenderSettings"; export class GameView { private renderer: GPURenderer; diff --git a/src/client/render/gl/render-settings.ts b/src/client/render/gl/RenderSettings.ts similarity index 100% rename from src/client/render/gl/render-settings.ts rename to src/client/render/gl/RenderSettings.ts diff --git a/src/client/render/gl/renderer.ts b/src/client/render/gl/Renderer.ts similarity index 94% rename from src/client/render/gl/renderer.ts rename to src/client/render/gl/Renderer.ts index d715217122..c174aa32da 100644 --- a/src/client/render/gl/renderer.ts +++ b/src/client/render/gl/Renderer.ts @@ -25,52 +25,52 @@ import type { TilePair, UnitState, } from "../types"; -import { Camera } from "./camera"; -import type { RadialMenuItem } from "./events"; -import { BarPass } from "./passes/bar-pass"; -import { BorderComputePass } from "./passes/border-compute-pass"; -import { BorderStampPass } from "./passes/border-stamp-pass"; -import { ConquestPopupPass } from "./passes/conquest-popup-pass"; -import { CoordinateGridPass } from "./passes/coordinate-grid-pass"; -import { CrosshairPass } from "./passes/crosshair-pass"; -import { FalloutBloomPass } from "./passes/fallout-bloom-pass"; -import { FalloutLightPass } from "./passes/fallout-light-pass"; +import { Camera } from "./Camera"; +import type { RadialMenuItem } from "./Events"; +import { BarPass } from "./passes/BarPass"; +import { BorderComputePass } from "./passes/BorderComputePass"; +import { BorderStampPass } from "./passes/BorderStampPass"; +import { ConquestPopupPass } from "./passes/ConquestPopupPass"; +import { CoordinateGridPass } from "./passes/CoordinateGridPass"; +import { CrosshairPass } from "./passes/CrosshairPass"; +import { FalloutBloomPass } from "./passes/FalloutBloomPass"; +import { FalloutLightPass } from "./passes/FalloutLightPass"; import { FxPass } from "./passes/fx-pass"; -import { LightmapPass } from "./passes/lightmap-pass"; -import { MoveIndicatorPass } from "./passes/move-indicator-pass"; +import { LightmapPass } from "./passes/LightmapPass"; +import { MoveIndicatorPass } from "./passes/MoveIndicatorPass"; import { NamePass } from "./passes/name-pass"; -import { NightCompositePass } from "./passes/night-composite-pass"; -import { NukeTelegraphPass } from "./passes/nuke-telegraph-pass"; -import { NukeTrajectoryPass } from "./passes/nuke-trajectory-pass"; -import { PointLightPass } from "./passes/point-light-pass"; -import { RadialMenuPass } from "./passes/radial-menu-pass"; -import { RailroadPass } from "./passes/railroad-pass"; -import { RangeCirclePass } from "./passes/range-circle-pass"; -import { SAMRadiusPass } from "./passes/sam-radius-pass"; -import { SelectionBoxPass } from "./passes/selection-box-pass"; -import type { SpawnCenter } from "./passes/spawn-overlay-pass"; -import { SpawnOverlayPass } from "./passes/spawn-overlay-pass"; -import { StructureLevelPass } from "./passes/structure-level-pass"; -import { StructurePass } from "./passes/structure-pass"; -import { TerrainPass } from "./passes/terrain-pass"; -import { TerritoryPass } from "./passes/territory-pass"; -import { TrailPass } from "./passes/trail-pass"; -import { UnitPass } from "./passes/unit-pass"; -import { createRenderSettings, type RenderSettings } from "./render-settings"; -import { AffiliationPalette } from "./utils/affiliation"; -import { buildTerrainRGBA, getPaletteSize } from "./utils/color-utils"; +import { NightCompositePass } from "./passes/NightCompositePass"; +import { NukeTelegraphPass } from "./passes/NukeTelegraphPass"; +import { NukeTrajectoryPass } from "./passes/NukeTrajectoryPass"; +import { PointLightPass } from "./passes/PointLightPass"; +import { RadialMenuPass } from "./passes/RadialMenuPass"; +import { RailroadPass } from "./passes/RailroadPass"; +import { RangeCirclePass } from "./passes/RangeCirclePass"; +import { SAMRadiusPass } from "./passes/SamRadiusPass"; +import { SelectionBoxPass } from "./passes/SelectionBoxPass"; +import type { SpawnCenter } from "./passes/SpawnOverlayPass"; +import { SpawnOverlayPass } from "./passes/SpawnOverlayPass"; +import { StructureLevelPass } from "./passes/StructureLevelPass"; +import { StructurePass } from "./passes/StructurePass"; +import { TerrainPass } from "./passes/TerrainPass"; +import { TerritoryPass } from "./passes/TerritoryPass"; +import { TrailPass } from "./passes/TrailPass"; +import { UnitPass } from "./passes/UnitPass"; +import { createRenderSettings, type RenderSettings } from "./RenderSettings"; +import { AffiliationPalette } from "./utils/Affiliation"; +import { buildTerrainRGBA, getPaletteSize } from "./utils/ColorUtils"; import { createTexture2D, toScreen, toTarget, type RenderTarget, -} from "./utils/gl-utils"; +} from "./utils/GlUtils"; import { createGPUResources, disposeGPUResources, type GPUResources, -} from "./utils/gpu-resources"; -import { HeatManager } from "./utils/heat-manager"; +} from "./utils/GpuResources"; +import { HeatManager } from "./utils/HeatManager"; /** Ghost types that trigger SAM radius overlay (matches upstream SAMRadiusLayer). */ const SAM_RADIUS_GHOST_TYPES = new Set([ @@ -166,7 +166,7 @@ export class GPURenderer { // Warship selection — supports any number of selections. private selectedUnitIds: number[] = []; /** Reusable scratch buffer of {x,y,r,g,b} for the selection-box pass. */ - private readonly selectionBoxEntries: import("./passes/selection-box-pass").SelectionEntry[] = + private readonly selectionBoxEntries: import("./passes/SelectionBoxPass").SelectionEntry[] = []; constructor( diff --git a/src/client/render/gl/settings-utils.ts b/src/client/render/gl/SettingsUtils.ts similarity index 100% rename from src/client/render/gl/settings-utils.ts rename to src/client/render/gl/SettingsUtils.ts diff --git a/src/client/render/gl/debug/config-prop.ts b/src/client/render/gl/debug/ConfigProp.ts similarity index 100% rename from src/client/render/gl/debug/config-prop.ts rename to src/client/render/gl/debug/ConfigProp.ts diff --git a/src/client/render/gl/debug/folder.ts b/src/client/render/gl/debug/Folder.ts similarity index 87% rename from src/client/render/gl/debug/folder.ts rename to src/client/render/gl/debug/Folder.ts index 97e9e20779..69bb0e44b2 100644 --- a/src/client/render/gl/debug/folder.ts +++ b/src/client/render/gl/debug/Folder.ts @@ -1,4 +1,4 @@ -import type { ConfigProp } from "./config-prop"; +import type { ConfigProp } from "./ConfigProp"; export interface FolderNode { kind: "folder"; diff --git a/src/client/render/gl/debug/layout.ts b/src/client/render/gl/debug/Layout.ts similarity index 98% rename from src/client/render/gl/debug/layout.ts rename to src/client/render/gl/debug/Layout.ts index e94f2ef9bd..67077228e1 100644 --- a/src/client/render/gl/debug/layout.ts +++ b/src/client/render/gl/debug/Layout.ts @@ -1,10 +1,10 @@ -import type { RenderSettings } from "../render-settings"; -import type { DebugNode } from "./folder"; -import { folder } from "./folder"; -import { color } from "./props/color"; -import { select } from "./props/select"; -import { slider } from "./props/slider"; -import { toggle } from "./props/toggle"; +import type { RenderSettings } from "../RenderSettings"; +import type { DebugNode } from "./Folder"; +import { folder } from "./Folder"; +import { color } from "./props/Color"; +import { select } from "./props/Select"; +import { slider } from "./props/Slider"; +import { toggle } from "./props/Toggle"; export function buildTree(s: RenderSettings, d: RenderSettings): DebugNode[] { return [ diff --git a/src/client/render/gl/debug/tree.ts b/src/client/render/gl/debug/Tree.ts similarity index 85% rename from src/client/render/gl/debug/tree.ts rename to src/client/render/gl/debug/Tree.ts index e7caa69954..8b5a06a234 100644 --- a/src/client/render/gl/debug/tree.ts +++ b/src/client/render/gl/debug/Tree.ts @@ -1,6 +1,6 @@ import GUI from "lil-gui"; -import type { ConfigProp } from "./config-prop"; -import type { DebugNode, FolderNode } from "./folder"; +import type { ConfigProp } from "./ConfigProp"; +import type { DebugNode, FolderNode } from "./Folder"; /** Walk the debug tree, drawing each node onto the GUI. Returns all leaf props. */ export function walkTree(nodes: DebugNode[], parent: GUI): ConfigProp[] { diff --git a/src/client/render/gl/debug/wiring.ts b/src/client/render/gl/debug/Wiring.ts similarity index 96% rename from src/client/render/gl/debug/wiring.ts rename to src/client/render/gl/debug/Wiring.ts index 1af7ef0aee..9511ac9c49 100644 --- a/src/client/render/gl/debug/wiring.ts +++ b/src/client/render/gl/debug/Wiring.ts @@ -1,8 +1,8 @@ import GUI, { FunctionController } from "lil-gui"; -import type { RenderSettings } from "../render-settings"; -import { createRenderSettings, dumpSettings } from "../render-settings"; -import { deepAssign } from "../settings-utils"; -import type { ConfigProp } from "./config-prop"; +import type { RenderSettings } from "../RenderSettings"; +import { createRenderSettings, dumpSettings } from "../RenderSettings"; +import { deepAssign } from "../SettingsUtils"; +import type { ConfigProp } from "./ConfigProp"; // --------------------------------------------------------------------------- // Draggable title bar diff --git a/src/client/render/gl/debug/index.ts b/src/client/render/gl/debug/index.ts index 10077477df..7a414c4641 100644 --- a/src/client/render/gl/debug/index.ts +++ b/src/client/render/gl/debug/index.ts @@ -1,9 +1,9 @@ import GUI from "lil-gui"; -import type { RenderSettings } from "../render-settings"; -import { createRenderSettings } from "../render-settings"; -import { buildTree } from "./layout"; -import { walkTree } from "./tree"; -import { makeDraggable, wireActions, wireModifiedIndicators } from "./wiring"; +import type { RenderSettings } from "../RenderSettings"; +import { createRenderSettings } from "../RenderSettings"; +import { buildTree } from "./Layout"; +import { walkTree } from "./Tree"; +import { makeDraggable, wireActions, wireModifiedIndicators } from "./Wiring"; export function createDebugGui( settings: RenderSettings, diff --git a/src/client/render/gl/debug/props/color.ts b/src/client/render/gl/debug/props/Color.ts similarity index 97% rename from src/client/render/gl/debug/props/color.ts rename to src/client/render/gl/debug/props/Color.ts index 095072a225..cfcebc4c44 100644 --- a/src/client/render/gl/debug/props/color.ts +++ b/src/client/render/gl/debug/props/Color.ts @@ -1,6 +1,6 @@ import type GUI from "lil-gui"; import type { ColorController, Controller } from "lil-gui"; -import type { ConfigProp } from "../config-prop"; +import type { ConfigProp } from "../ConfigProp"; export function color>( target: T, diff --git a/src/client/render/gl/debug/props/select.ts b/src/client/render/gl/debug/props/Select.ts similarity index 93% rename from src/client/render/gl/debug/props/select.ts rename to src/client/render/gl/debug/props/Select.ts index 34ac0a8933..12f6289ef5 100644 --- a/src/client/render/gl/debug/props/select.ts +++ b/src/client/render/gl/debug/props/Select.ts @@ -1,6 +1,6 @@ import type GUI from "lil-gui"; import type { Controller } from "lil-gui"; -import type { ConfigProp } from "../config-prop"; +import type { ConfigProp } from "../ConfigProp"; export function select>( target: T, diff --git a/src/client/render/gl/debug/props/slider.ts b/src/client/render/gl/debug/props/Slider.ts similarity index 93% rename from src/client/render/gl/debug/props/slider.ts rename to src/client/render/gl/debug/props/Slider.ts index 016591dc4e..d9b164a403 100644 --- a/src/client/render/gl/debug/props/slider.ts +++ b/src/client/render/gl/debug/props/Slider.ts @@ -1,6 +1,6 @@ import type GUI from "lil-gui"; import type { Controller } from "lil-gui"; -import type { ConfigProp } from "../config-prop"; +import type { ConfigProp } from "../ConfigProp"; export function slider>( target: T, diff --git a/src/client/render/gl/debug/props/toggle.ts b/src/client/render/gl/debug/props/Toggle.ts similarity index 93% rename from src/client/render/gl/debug/props/toggle.ts rename to src/client/render/gl/debug/props/Toggle.ts index 0429fbefec..ec6fb03f77 100644 --- a/src/client/render/gl/debug/props/toggle.ts +++ b/src/client/render/gl/debug/props/Toggle.ts @@ -1,6 +1,6 @@ import type GUI from "lil-gui"; import type { Controller } from "lil-gui"; -import type { ConfigProp } from "../config-prop"; +import type { ConfigProp } from "../ConfigProp"; export function toggle>( target: T, diff --git a/src/client/render/gl/index.ts b/src/client/render/gl/index.ts index 2b0942f038..3f30aab33e 100644 --- a/src/client/render/gl/index.ts +++ b/src/client/render/gl/index.ts @@ -7,15 +7,15 @@ export type { MapScrollEvent, RadialMenuItem, RadialMenuSelectEvent, -} from "./events"; -export { GameView } from "./game-view"; -export type { SpawnCenter } from "./passes/spawn-overlay-pass"; -export { createRenderSettings, dumpSettings } from "./render-settings"; -export type { RenderSettings } from "./render-settings"; -export { deepAssign, deepDiff } from "./settings-utils"; -export { buildTerrainRGBA, getPaletteSize } from "./utils/color-utils"; -export { buildNukeTrajectory, samRange } from "./utils/nuke-trajectory"; -export type { SAMInfo } from "./utils/nuke-trajectory"; +} from "./Events"; +export { GameView } from "./GameView"; +export type { SpawnCenter } from "./passes/SpawnOverlayPass"; +export { createRenderSettings, dumpSettings } from "./RenderSettings"; +export type { RenderSettings } from "./RenderSettings"; +export { deepAssign, deepDiff } from "./SettingsUtils"; +export { buildTerrainRGBA, getPaletteSize } from "./utils/ColorUtils"; +export { buildNukeTrajectory, samRange } from "./utils/NukeTrajectory"; +export type { SAMInfo } from "./utils/NukeTrajectory"; // Re-export shared types used in the public API export type { diff --git a/src/client/render/gl/passes/bar-pass.ts b/src/client/render/gl/passes/BarPass.ts similarity index 98% rename from src/client/render/gl/passes/bar-pass.ts rename to src/client/render/gl/passes/BarPass.ts index 8f14c8466c..7e1de38001 100644 --- a/src/client/render/gl/passes/bar-pass.ts +++ b/src/client/render/gl/passes/BarPass.ts @@ -15,11 +15,11 @@ import { DELETION_MARK_DURATION, missileReadiness, WARSHIP_MAX_HEALTH, -} from "../../game-constants"; +} from "../../GameConstants"; import type { RendererConfig, UnitState } from "../../types"; import { UT_MISSILE_SILO, UT_SAM_LAUNCHER } from "../../types"; -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; import barFragSrc from "../shaders/bar/bar.frag.glsl?raw"; import barVertSrc from "../shaders/bar/bar.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/border-compute-pass.ts b/src/client/render/gl/passes/BorderComputePass.ts similarity index 98% rename from src/client/render/gl/passes/border-compute-pass.ts rename to src/client/render/gl/passes/BorderComputePass.ts index 625aefd00c..e6e0910e53 100644 --- a/src/client/render/gl/passes/border-compute-pass.ts +++ b/src/client/render/gl/passes/BorderComputePass.ts @@ -12,7 +12,7 @@ * computed once here via an N-tile Chebyshev radius expansion. */ -import type { RenderSettings } from "../render-settings"; +import type { RenderSettings } from "../RenderSettings"; import borderComputeFragSrc from "../shaders/border-compute/border-compute.frag.glsl?raw"; import fullscreenNoUvVertSrc from "../shaders/shared/fullscreen-no-uv.vert.glsl?raw"; import { @@ -20,8 +20,8 @@ import { createProgram, createTexture2D, shaderSrc, -} from "../utils/gl-utils"; -import { TILE_DEFINES } from "../utils/tile-codec"; +} from "../utils/GlUtils"; +import { TILE_DEFINES } from "../utils/TileCodec"; const MAX_DEFENSE_POSTS = 64; diff --git a/src/client/render/gl/passes/border-stamp-pass.ts b/src/client/render/gl/passes/BorderStampPass.ts similarity index 96% rename from src/client/render/gl/passes/border-stamp-pass.ts rename to src/client/render/gl/passes/BorderStampPass.ts index 4d247c1141..9334a3b920 100644 --- a/src/client/render/gl/passes/border-stamp-pass.ts +++ b/src/client/render/gl/passes/BorderStampPass.ts @@ -6,10 +6,10 @@ * from the BorderComputePass RGBA8 buffer. */ -import type { RenderSettings } from "../render-settings"; -import { getPaletteSize } from "../utils/color-utils"; -import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; -import { TILE_DEFINES } from "../utils/tile-codec"; +import type { RenderSettings } from "../RenderSettings"; +import { getPaletteSize } from "../utils/ColorUtils"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/GlUtils"; +import { TILE_DEFINES } from "../utils/TileCodec"; import borderStampFragSrc from "../shaders/day-night/border-stamp.frag.glsl?raw"; import borderStampVertSrc from "../shaders/day-night/border-stamp.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/conquest-popup-pass.ts b/src/client/render/gl/passes/ConquestPopupPass.ts similarity index 96% rename from src/client/render/gl/passes/conquest-popup-pass.ts rename to src/client/render/gl/passes/ConquestPopupPass.ts index 6af9909d94..ad8959bdd2 100644 --- a/src/client/render/gl/passes/conquest-popup-pass.ts +++ b/src/client/render/gl/passes/ConquestPopupPass.ts @@ -7,13 +7,13 @@ */ import type { BonusEvent, ConquestFx } from "../../types"; -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; -import type { GlyphTables } from "./name-pass/atlas-data"; -import { buildGlyphTables, parseAtlasData } from "./name-pass/atlas-data"; -import { buildGlyphMetricsTex } from "./name-pass/data-textures"; -import { layoutString } from "./name-pass/text-layout"; -import { CHAR_RANGE, MAX_CHARS } from "./name-pass/types"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; +import type { GlyphTables } from "./name-pass/AtlasData"; +import { buildGlyphTables, parseAtlasData } from "./name-pass/AtlasData"; +import { buildGlyphMetricsTex } from "./name-pass/DataTextures"; +import { layoutString } from "./name-pass/TextLayout"; +import { CHAR_RANGE, MAX_CHARS } from "./name-pass/Types"; import { assetUrl } from "src/core/AssetUrls"; import fragSrc from "../shaders/conquest-popup/conquest-popup.frag.glsl?raw"; diff --git a/src/client/render/gl/passes/coordinate-grid-pass.ts b/src/client/render/gl/passes/CoordinateGridPass.ts similarity index 97% rename from src/client/render/gl/passes/coordinate-grid-pass.ts rename to src/client/render/gl/passes/CoordinateGridPass.ts index b9a9f0e2f3..5993a02d60 100644 --- a/src/client/render/gl/passes/coordinate-grid-pass.ts +++ b/src/client/render/gl/passes/CoordinateGridPass.ts @@ -6,8 +6,8 @@ * the upstream game's CoordinateGridLayer. */ -import type { RenderSettings } from "../render-settings"; -import { createMapQuad, createProgram } from "../utils/gl-utils"; +import type { RenderSettings } from "../RenderSettings"; +import { createMapQuad, createProgram } from "../utils/GlUtils"; import gridFragSrc from "../shaders/grid/grid.frag.glsl?raw"; import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/crosshair-pass.ts b/src/client/render/gl/passes/CrosshairPass.ts similarity index 98% rename from src/client/render/gl/passes/crosshair-pass.ts rename to src/client/render/gl/passes/CrosshairPass.ts index 9d07fe370f..77cef1c2be 100644 --- a/src/client/render/gl/passes/crosshair-pass.ts +++ b/src/client/render/gl/passes/CrosshairPass.ts @@ -8,7 +8,7 @@ import type { GhostPreviewData } from "../../types"; import { UT_MIRV, UT_WARSHIP } from "../../types"; -import { createProgram } from "../utils/gl-utils"; +import { createProgram } from "../utils/GlUtils"; import fragSrc from "../shaders/crosshair/crosshair.frag.glsl?raw"; import vertSrc from "../shaders/crosshair/crosshair.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/fallout-bloom-pass.ts b/src/client/render/gl/passes/FalloutBloomPass.ts similarity index 98% rename from src/client/render/gl/passes/fallout-bloom-pass.ts rename to src/client/render/gl/passes/FalloutBloomPass.ts index 62c6b27f71..dbbc5fdbf0 100644 --- a/src/client/render/gl/passes/fallout-bloom-pass.ts +++ b/src/client/render/gl/passes/FalloutBloomPass.ts @@ -9,15 +9,15 @@ * Heat management is handled by HeatManager (shared with LightmapPass). */ -import type { RenderSettings } from "../render-settings"; +import type { RenderSettings } from "../RenderSettings"; import { createFullscreenQuad, createMapQuad, createProgram, shaderSrc, -} from "../utils/gl-utils"; -import type { HeatManager } from "../utils/heat-manager"; -import { TILE_DEFINES } from "../utils/tile-codec"; +} from "../utils/GlUtils"; +import type { HeatManager } from "../utils/HeatManager"; +import { TILE_DEFINES } from "../utils/TileCodec"; import compositeFragSrc from "../shaders/fallout-bloom/composite.frag.glsl?raw"; import compositeVertSrc from "../shaders/fallout-bloom/composite.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/fallout-light-pass.ts b/src/client/render/gl/passes/FalloutLightPass.ts similarity index 97% rename from src/client/render/gl/passes/fallout-light-pass.ts rename to src/client/render/gl/passes/FalloutLightPass.ts index 15828cfe58..67d065d9bf 100644 --- a/src/client/render/gl/passes/fallout-light-pass.ts +++ b/src/client/render/gl/passes/FalloutLightPass.ts @@ -6,15 +6,15 @@ * 2. Composite into the target lightmap FBO via camera-projected map quad (additive) */ -import type { RenderSettings } from "../render-settings"; +import type { RenderSettings } from "../RenderSettings"; import { createFullscreenQuad, createMapQuad, createProgram, shaderSrc, -} from "../utils/gl-utils"; -import type { HeatManager } from "../utils/heat-manager"; -import { TILE_DEFINES } from "../utils/tile-codec"; +} from "../utils/GlUtils"; +import type { HeatManager } from "../utils/HeatManager"; +import { TILE_DEFINES } from "../utils/TileCodec"; import falloutCompositeFragSrc from "../shaders/day-night/fallout-composite.frag.glsl?raw"; import falloutCompositeVertSrc from "../shaders/day-night/fallout-composite.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/lightmap-pass.ts b/src/client/render/gl/passes/LightmapPass.ts similarity index 95% rename from src/client/render/gl/passes/lightmap-pass.ts rename to src/client/render/gl/passes/LightmapPass.ts index 8107abe6e5..8a74a57f2a 100644 --- a/src/client/render/gl/passes/lightmap-pass.ts +++ b/src/client/render/gl/passes/LightmapPass.ts @@ -5,10 +5,10 @@ * Delegates light rendering to PointLightPass and FalloutLightPass. */ -import type { RenderSettings } from "../render-settings"; -import { createFullscreenQuad, createProgram } from "../utils/gl-utils"; -import type { FalloutLightPass } from "./fallout-light-pass"; -import type { PointLightPass } from "./point-light-pass"; +import type { RenderSettings } from "../RenderSettings"; +import { createFullscreenQuad, createProgram } from "../utils/GlUtils"; +import type { FalloutLightPass } from "./FalloutLightPass"; +import type { PointLightPass } from "./PointLightPass"; import blurFragSrc from "../shaders/shared/blur.frag.glsl?raw"; import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/move-indicator-pass.ts b/src/client/render/gl/passes/MoveIndicatorPass.ts similarity index 97% rename from src/client/render/gl/passes/move-indicator-pass.ts rename to src/client/render/gl/passes/MoveIndicatorPass.ts index 42c8ac30ba..0279fbb3e9 100644 --- a/src/client/render/gl/passes/move-indicator-pass.ts +++ b/src/client/render/gl/passes/MoveIndicatorPass.ts @@ -4,8 +4,8 @@ * but rendered via SDF in a fragment shader. */ -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; import fragSrc from "../shaders/move-indicator/move-indicator.frag.glsl?raw"; import vertSrc from "../shaders/move-indicator/move-indicator.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/night-composite-pass.ts b/src/client/render/gl/passes/NightCompositePass.ts similarity index 95% rename from src/client/render/gl/passes/night-composite-pass.ts rename to src/client/render/gl/passes/NightCompositePass.ts index a8616fafbd..13e956e68e 100644 --- a/src/client/render/gl/passes/night-composite-pass.ts +++ b/src/client/render/gl/passes/NightCompositePass.ts @@ -9,8 +9,8 @@ * multiplication by ~1.0 — so the pass runs continuously with no threshold. */ -import type { RenderSettings } from "../render-settings"; -import { createFullscreenQuad, createProgram } from "../utils/gl-utils"; +import type { RenderSettings } from "../RenderSettings"; +import { createFullscreenQuad, createProgram } from "../utils/GlUtils"; import compositeFragSrc from "../shaders/day-night/composite.frag.glsl?raw"; import fullscreenVertSrc from "../shaders/shared/fullscreen.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/nuke-telegraph-pass.ts b/src/client/render/gl/passes/NukeTelegraphPass.ts similarity index 96% rename from src/client/render/gl/passes/nuke-telegraph-pass.ts rename to src/client/render/gl/passes/NukeTelegraphPass.ts index 17ee6a4198..819ebf8b57 100644 --- a/src/client/render/gl/passes/nuke-telegraph-pass.ts +++ b/src/client/render/gl/passes/NukeTelegraphPass.ts @@ -7,9 +7,9 @@ */ import type { NukeTelegraphData } from "../../types"; -import { DynamicInstanceBuffer } from "../dynamic-buffer"; -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; +import { DynamicInstanceBuffer } from "../DynamicBuffer"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; import fragSrc from "../shaders/nuke-telegraph/nuke-telegraph.frag.glsl?raw"; import vertSrc from "../shaders/nuke-telegraph/nuke-telegraph.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/nuke-trajectory-pass.ts b/src/client/render/gl/passes/NukeTrajectoryPass.ts similarity index 99% rename from src/client/render/gl/passes/nuke-trajectory-pass.ts rename to src/client/render/gl/passes/NukeTrajectoryPass.ts index cb661bf7e5..328876677a 100644 --- a/src/client/render/gl/passes/nuke-trajectory-pass.ts +++ b/src/client/render/gl/passes/NukeTrajectoryPass.ts @@ -11,8 +11,8 @@ */ import type { NukeTrajectoryData } from "../../types"; -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; import markerFragSrc from "../shaders/nuke-trajectory/nuke-trajectory-marker.frag.glsl?raw"; import markerVertSrc from "../shaders/nuke-trajectory/nuke-trajectory-marker.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/point-light-pass.ts b/src/client/render/gl/passes/PointLightPass.ts similarity index 98% rename from src/client/render/gl/passes/point-light-pass.ts rename to src/client/render/gl/passes/PointLightPass.ts index bbe535fac5..bbc0abea5f 100644 --- a/src/client/render/gl/passes/point-light-pass.ts +++ b/src/client/render/gl/passes/PointLightPass.ts @@ -22,8 +22,8 @@ import { UT_TRANSPORT, UT_WARSHIP, } from "../../types"; -import type { RenderSettings } from "../render-settings"; -import { createProgram, shaderSrc } from "../utils/gl-utils"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram, shaderSrc } from "../utils/GlUtils"; import lightFragSrc from "../shaders/day-night/light.frag.glsl?raw"; import lightVertSrc from "../shaders/day-night/light.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/radial-menu-pass.ts b/src/client/render/gl/passes/RadialMenuPass.ts similarity index 99% rename from src/client/render/gl/passes/radial-menu-pass.ts rename to src/client/render/gl/passes/RadialMenuPass.ts index b394550ed6..2518298ec4 100644 --- a/src/client/render/gl/passes/radial-menu-pass.ts +++ b/src/client/render/gl/passes/RadialMenuPass.ts @@ -12,8 +12,8 @@ * 3. Icons: instanced quads sampling the emoji atlas */ -import type { RadialMenuItem } from "../events"; -import { createProgram } from "../utils/gl-utils"; +import type { RadialMenuItem } from "../Events"; +import { createProgram } from "../utils/GlUtils"; import arcFragSrc from "../shaders/radial-menu/arcs.frag.glsl?raw"; import arcVertSrc from "../shaders/radial-menu/arcs.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/railroad-pass.ts b/src/client/render/gl/passes/RailroadPass.ts similarity index 98% rename from src/client/render/gl/passes/railroad-pass.ts rename to src/client/render/gl/passes/RailroadPass.ts index 1892f8d663..4c3b89a43e 100644 --- a/src/client/render/gl/passes/railroad-pass.ts +++ b/src/client/render/gl/passes/RailroadPass.ts @@ -17,17 +17,17 @@ */ import type { GhostPreviewData } from "../../types"; -import type { RenderSettings } from "../render-settings"; +import type { RenderSettings } from "../RenderSettings"; import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; import railroadFragSrc from "../shaders/railroad/railroad.frag.glsl?raw"; -import { getPaletteSize } from "../utils/color-utils"; +import { getPaletteSize } from "../utils/ColorUtils"; import { createMapQuad, createProgram, createTexture2D, shaderSrc, -} from "../utils/gl-utils"; -import { TILE_DEFINES } from "../utils/tile-codec"; +} from "../utils/GlUtils"; +import { TILE_DEFINES } from "../utils/TileCodec"; // --------------------------------------------------------------------------- // Rail orientation (0-5) → texture value (1-6, 0=none) diff --git a/src/client/render/gl/passes/range-circle-pass.ts b/src/client/render/gl/passes/RangeCirclePass.ts similarity index 97% rename from src/client/render/gl/passes/range-circle-pass.ts rename to src/client/render/gl/passes/RangeCirclePass.ts index 33241e6cc6..5fb38e8059 100644 --- a/src/client/render/gl/passes/range-circle-pass.ts +++ b/src/client/render/gl/passes/RangeCirclePass.ts @@ -7,7 +7,7 @@ */ import type { GhostPreviewData } from "../../types"; -import { createProgram } from "../utils/gl-utils"; +import { createProgram } from "../utils/GlUtils"; import fragSrc from "../shaders/range-circle/range-circle.frag.glsl?raw"; import vertSrc from "../shaders/range-circle/range-circle.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/sam-radius-pass.ts b/src/client/render/gl/passes/SamRadiusPass.ts similarity index 98% rename from src/client/render/gl/passes/sam-radius-pass.ts rename to src/client/render/gl/passes/SamRadiusPass.ts index b5cf0cc980..e4fd45a32f 100644 --- a/src/client/render/gl/passes/sam-radius-pass.ts +++ b/src/client/render/gl/passes/SamRadiusPass.ts @@ -15,10 +15,10 @@ import type { UnitState } from "../../types"; import { UT_SAM_LAUNCHER } from "../../types"; -import { DynamicInstanceBuffer } from "../dynamic-buffer"; -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; -import { samRange } from "../utils/nuke-trajectory"; +import { DynamicInstanceBuffer } from "../DynamicBuffer"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; +import { samRange } from "../utils/NukeTrajectory"; import fragSrc from "../shaders/sam-radius/sam-radius.frag.glsl?raw"; import vertSrc from "../shaders/sam-radius/sam-radius.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/selection-box-pass.ts b/src/client/render/gl/passes/SelectionBoxPass.ts similarity index 98% rename from src/client/render/gl/passes/selection-box-pass.ts rename to src/client/render/gl/passes/SelectionBoxPass.ts index 2e74c475b4..28bb930367 100644 --- a/src/client/render/gl/passes/selection-box-pass.ts +++ b/src/client/render/gl/passes/SelectionBoxPass.ts @@ -6,7 +6,7 @@ * this ever becomes hot we could swap to instanced rendering. */ -import { createProgram } from "../utils/gl-utils"; +import { createProgram } from "../utils/GlUtils"; import fragSrc from "../shaders/selection-box/selection-box.frag.glsl?raw"; import vertSrc from "../shaders/selection-box/selection-box.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/spawn-overlay-pass.ts b/src/client/render/gl/passes/SpawnOverlayPass.ts similarity index 97% rename from src/client/render/gl/passes/spawn-overlay-pass.ts rename to src/client/render/gl/passes/SpawnOverlayPass.ts index 7d9936e126..99f446d559 100644 --- a/src/client/render/gl/passes/spawn-overlay-pass.ts +++ b/src/client/render/gl/passes/SpawnOverlayPass.ts @@ -11,9 +11,9 @@ * effects in tile-space coordinates. */ -import type { RenderSettings } from "../render-settings"; -import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; -import { TILE_DEFINES } from "../utils/tile-codec"; +import type { RenderSettings } from "../RenderSettings"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/GlUtils"; +import { TILE_DEFINES } from "../utils/TileCodec"; import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; import spawnFragSrc from "../shaders/spawn-overlay/spawn-overlay.frag.glsl?raw"; diff --git a/src/client/render/gl/passes/structure-level-pass.ts b/src/client/render/gl/passes/StructureLevelPass.ts similarity index 95% rename from src/client/render/gl/passes/structure-level-pass.ts rename to src/client/render/gl/passes/StructureLevelPass.ts index 04f07605c1..a727e31e92 100644 --- a/src/client/render/gl/passes/structure-level-pass.ts +++ b/src/client/render/gl/passes/StructureLevelPass.ts @@ -17,14 +17,14 @@ import { UT_PORT, UT_SAM_LAUNCHER, } from "../../types"; -import { DynamicInstanceBuffer } from "../dynamic-buffer"; -import type { RenderSettings } from "../render-settings"; -import { createProgram } from "../utils/gl-utils"; -import type { GlyphTables } from "./name-pass/atlas-data"; -import { buildGlyphTables, parseAtlasData } from "./name-pass/atlas-data"; -import { buildGlyphMetricsTex } from "./name-pass/data-textures"; -import { layoutString } from "./name-pass/text-layout"; -import { CHAR_RANGE, MAX_CHARS } from "./name-pass/types"; +import { DynamicInstanceBuffer } from "../DynamicBuffer"; +import type { RenderSettings } from "../RenderSettings"; +import { createProgram } from "../utils/GlUtils"; +import type { GlyphTables } from "./name-pass/AtlasData"; +import { buildGlyphTables, parseAtlasData } from "./name-pass/AtlasData"; +import { buildGlyphMetricsTex } from "./name-pass/DataTextures"; +import { layoutString } from "./name-pass/TextLayout"; +import { CHAR_RANGE, MAX_CHARS } from "./name-pass/Types"; import { assetUrl } from "src/core/AssetUrls"; import fragSrc from "../shaders/structure-level/structure-level.frag.glsl?raw"; diff --git a/src/client/render/gl/passes/structure-pass.ts b/src/client/render/gl/passes/StructurePass.ts similarity index 98% rename from src/client/render/gl/passes/structure-pass.ts rename to src/client/render/gl/passes/StructurePass.ts index 3e14d9d474..42f153143c 100644 --- a/src/client/render/gl/passes/structure-pass.ts +++ b/src/client/render/gl/passes/StructurePass.ts @@ -23,10 +23,10 @@ import { UT_PORT, UT_SAM_LAUNCHER, } from "../../types"; -import { DynamicInstanceBuffer } from "../dynamic-buffer"; -import type { RenderSettings } from "../render-settings"; -import { getPaletteSize } from "../utils/color-utils"; -import { createProgram, shaderSrc } from "../utils/gl-utils"; +import { DynamicInstanceBuffer } from "../DynamicBuffer"; +import type { RenderSettings } from "../RenderSettings"; +import { getPaletteSize } from "../utils/ColorUtils"; +import { createProgram, shaderSrc } from "../utils/GlUtils"; import { assetUrl } from "src/core/AssetUrls"; import structureFragSrc from "../shaders/structure/structure.frag.glsl?raw"; diff --git a/src/client/render/gl/passes/terrain-pass.ts b/src/client/render/gl/passes/TerrainPass.ts similarity index 98% rename from src/client/render/gl/passes/terrain-pass.ts rename to src/client/render/gl/passes/TerrainPass.ts index 812f43c138..b5d16777aa 100644 --- a/src/client/render/gl/passes/terrain-pass.ts +++ b/src/client/render/gl/passes/TerrainPass.ts @@ -16,7 +16,7 @@ import { createProgram, createTexture2D, shaderSrc, -} from "../utils/gl-utils"; +} from "../utils/GlUtils"; // --------------------------------------------------------------------------- // TerrainPass diff --git a/src/client/render/gl/passes/territory-pass.ts b/src/client/render/gl/passes/TerritoryPass.ts similarity index 98% rename from src/client/render/gl/passes/territory-pass.ts rename to src/client/render/gl/passes/TerritoryPass.ts index 63c0a664ad..2469507464 100644 --- a/src/client/render/gl/passes/territory-pass.ts +++ b/src/client/render/gl/passes/TerritoryPass.ts @@ -13,10 +13,10 @@ */ import type { TilePair } from "../../types"; -import type { RenderSettings } from "../render-settings"; -import { getPaletteSize } from "../utils/color-utils"; -import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; -import { OWNER_MASK, TILE_DEFINES } from "../utils/tile-codec"; +import type { RenderSettings } from "../RenderSettings"; +import { getPaletteSize } from "../utils/ColorUtils"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/GlUtils"; +import { OWNER_MASK, TILE_DEFINES } from "../utils/TileCodec"; import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; import territoryFragSrc from "../shaders/map-overlay/territory.frag.glsl?raw"; diff --git a/src/client/render/gl/passes/trail-pass.ts b/src/client/render/gl/passes/TrailPass.ts similarity index 94% rename from src/client/render/gl/passes/trail-pass.ts rename to src/client/render/gl/passes/TrailPass.ts index 5cad9b7376..db1cd40ff2 100644 --- a/src/client/render/gl/passes/trail-pass.ts +++ b/src/client/render/gl/passes/TrailPass.ts @@ -6,10 +6,10 @@ * Always draws at full brightness (after night composite). */ -import type { RenderSettings } from "../render-settings"; -import { getPaletteSize } from "../utils/color-utils"; -import { createMapQuad, createProgram, shaderSrc } from "../utils/gl-utils"; -import { TILE_DEFINES } from "../utils/tile-codec"; +import type { RenderSettings } from "../RenderSettings"; +import { getPaletteSize } from "../utils/ColorUtils"; +import { createMapQuad, createProgram, shaderSrc } from "../utils/GlUtils"; +import { TILE_DEFINES } from "../utils/TileCodec"; import overlayVertSrc from "../shaders/map-overlay/overlay.vert.glsl?raw"; import trailFragSrc from "../shaders/map-overlay/trail.frag.glsl?raw"; diff --git a/src/client/render/gl/passes/unit-pass.ts b/src/client/render/gl/passes/UnitPass.ts similarity index 98% rename from src/client/render/gl/passes/unit-pass.ts rename to src/client/render/gl/passes/UnitPass.ts index 3d62851ecd..95322c6f9a 100644 --- a/src/client/render/gl/passes/unit-pass.ts +++ b/src/client/render/gl/passes/UnitPass.ts @@ -47,12 +47,12 @@ import { UT_TRANSPORT, UT_WARSHIP, } from "../../types"; -import { DynamicInstanceBuffer } from "../dynamic-buffer"; -import type { RenderSettings } from "../render-settings"; +import { DynamicInstanceBuffer } from "../DynamicBuffer"; +import type { RenderSettings } from "../RenderSettings"; import unitFragSrc from "../shaders/unit/unit.frag.glsl?raw"; import unitVertSrc from "../shaders/unit/unit.vert.glsl?raw"; -import { getPaletteSize } from "../utils/color-utils"; -import { createProgram, shaderSrc } from "../utils/gl-utils"; +import { getPaletteSize } from "../utils/ColorUtils"; +import { createProgram, shaderSrc } from "../utils/GlUtils"; const unitAtlasUrl = assetUrl("atlases/unit-atlas.png"); diff --git a/src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts b/src/client/render/gl/passes/fx-pass/FxAttackRingPass.ts similarity index 97% rename from src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts rename to src/client/render/gl/passes/fx-pass/FxAttackRingPass.ts index bc66eda300..c522e2880a 100644 --- a/src/client/render/gl/passes/fx-pass/fx-attack-ring-pass.ts +++ b/src/client/render/gl/passes/fx-pass/FxAttackRingPass.ts @@ -6,9 +6,9 @@ */ import type { AttackRingInput } from "../../../types"; -import { DynamicInstanceBuffer } from "../../dynamic-buffer"; -import type { RenderSettings } from "../../render-settings"; -import { createProgram } from "../../utils/gl-utils"; +import { DynamicInstanceBuffer } from "../../DynamicBuffer"; +import type { RenderSettings } from "../../RenderSettings"; +import { createProgram } from "../../utils/GlUtils"; import attackRingFragSrc from "../../shaders/fx/attack-ring.frag.glsl?raw"; import attackRingVertSrc from "../../shaders/fx/attack-ring.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts b/src/client/render/gl/passes/fx-pass/FxShockwavePass.ts similarity index 96% rename from src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts rename to src/client/render/gl/passes/fx-pass/FxShockwavePass.ts index 62d011986f..41f3e4598e 100644 --- a/src/client/render/gl/passes/fx-pass/fx-shockwave-pass.ts +++ b/src/client/render/gl/passes/fx-pass/FxShockwavePass.ts @@ -5,9 +5,9 @@ * Uses an SDF circle rendered in a unit quad, no texture required. */ -import { DynamicInstanceBuffer } from "../../dynamic-buffer"; -import type { RenderSettings } from "../../render-settings"; -import { createProgram } from "../../utils/gl-utils"; +import { DynamicInstanceBuffer } from "../../DynamicBuffer"; +import type { RenderSettings } from "../../RenderSettings"; +import { createProgram } from "../../utils/GlUtils"; import shockwaveFragSrc from "../../shaders/fx/shockwave.frag.glsl?raw"; import shockwaveVertSrc from "../../shaders/fx/shockwave.vert.glsl?raw"; diff --git a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts b/src/client/render/gl/passes/fx-pass/FxSpritePass.ts similarity index 98% rename from src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts rename to src/client/render/gl/passes/fx-pass/FxSpritePass.ts index 42788a6267..5937583d67 100644 --- a/src/client/render/gl/passes/fx-pass/fx-sprite-pass.ts +++ b/src/client/render/gl/passes/fx-pass/FxSpritePass.ts @@ -6,7 +6,7 @@ * Pre-built by generate-sprite-atlases.mjs. */ -import { MS_PER_TICK, NUKE_EXPLOSION_RADII } from "../../../game-constants"; +import { MS_PER_TICK, NUKE_EXPLOSION_RADII } from "../../../GameConstants"; import type { ConquestFx, DeadUnitFx, RendererConfig } from "../../../types"; import { STRUCTURE_TYPES, @@ -14,9 +14,9 @@ import { UT_TRAIN, UT_WARSHIP, } from "../../../types"; -import { DynamicInstanceBuffer } from "../../dynamic-buffer"; -import type { RenderSettings } from "../../render-settings"; -import { createProgram, shaderSrc } from "../../utils/gl-utils"; +import { DynamicInstanceBuffer } from "../../DynamicBuffer"; +import type { RenderSettings } from "../../RenderSettings"; +import { createProgram, shaderSrc } from "../../utils/GlUtils"; import fxAtlasMeta from "resources/atlases/fx-atlas-meta.json"; import { assetUrl } from "src/core/AssetUrls"; diff --git a/src/client/render/gl/passes/fx-pass/index.ts b/src/client/render/gl/passes/fx-pass/index.ts index b46a8b7bf4..12ec3c0b73 100644 --- a/src/client/render/gl/passes/fx-pass/index.ts +++ b/src/client/render/gl/passes/fx-pass/index.ts @@ -8,17 +8,17 @@ * interceptions) are coordinated here so each sub-pass stays self-contained. */ -import { MS_PER_TICK, NUKE_EXPLOSION_RADII } from "../../../game-constants"; +import { MS_PER_TICK, NUKE_EXPLOSION_RADII } from "../../../GameConstants"; import type { AttackRingInput, ConquestFx, DeadUnitFx, RendererConfig, } from "../../../types"; -import type { RenderSettings } from "../../render-settings"; -import { FxAttackRingPass } from "./fx-attack-ring-pass"; -import { FxShockwavePass } from "./fx-shockwave-pass"; -import { FxSpritePass } from "./fx-sprite-pass"; +import type { RenderSettings } from "../../RenderSettings"; +import { FxAttackRingPass } from "./FxAttackRingPass"; +import { FxShockwavePass } from "./FxShockwavePass"; +import { FxSpritePass } from "./FxSpritePass"; export type { AttackRingInput } from "../../../types"; diff --git a/src/client/render/gl/passes/name-pass/atlas-data.ts b/src/client/render/gl/passes/name-pass/AtlasData.ts similarity index 96% rename from src/client/render/gl/passes/name-pass/atlas-data.ts rename to src/client/render/gl/passes/name-pass/AtlasData.ts index 65f49a2854..e385a452f0 100644 --- a/src/client/render/gl/passes/name-pass/atlas-data.ts +++ b/src/client/render/gl/passes/name-pass/AtlasData.ts @@ -6,8 +6,8 @@ import emojiAtlasMeta from "resources/atlases/emoji-atlas-meta.json"; import flagAtlasMeta from "resources/atlases/flag-atlas-meta.json"; import atlasData from "resources/atlases/msdf-atlas.json"; -import type { BMChar, BMKerning, ParsedAtlas } from "./types"; -import { CHAR_RANGE } from "./types"; +import type { BMChar, BMKerning, ParsedAtlas } from "./Types"; +import { CHAR_RANGE } from "./Types"; // --------------------------------------------------------------------------- // Atlas parsing diff --git a/src/client/render/gl/passes/name-pass/data-textures.ts b/src/client/render/gl/passes/name-pass/DataTextures.ts similarity index 93% rename from src/client/render/gl/passes/name-pass/data-textures.ts rename to src/client/render/gl/passes/name-pass/DataTextures.ts index b97ceb5073..7738bd18ac 100644 --- a/src/client/render/gl/passes/name-pass/data-textures.ts +++ b/src/client/render/gl/passes/name-pass/DataTextures.ts @@ -3,9 +3,9 @@ * Uses createTexture2D from gl-utils to eliminate boilerplate. */ -import { createTexture2D } from "../../utils/gl-utils"; -import type { ParsedAtlas } from "./types"; -import { CHAR_RANGE, LINES_PER_PLAYER, MAX_CHARS } from "./types"; +import { createTexture2D } from "../../utils/GlUtils"; +import type { ParsedAtlas } from "./Types"; +import { CHAR_RANGE, LINES_PER_PLAYER, MAX_CHARS } from "./Types"; /** Glyph metrics: CHAR_RANGE x 2, RGBA32F. Static — uploaded once. */ export function buildGlyphMetricsTex( diff --git a/src/client/render/gl/passes/name-pass/debug-program.ts b/src/client/render/gl/passes/name-pass/DebugProgram.ts similarity index 94% rename from src/client/render/gl/passes/name-pass/debug-program.ts rename to src/client/render/gl/passes/name-pass/DebugProgram.ts index fb9ae01d55..c9c7e18efc 100644 --- a/src/client/render/gl/passes/name-pass/debug-program.ts +++ b/src/client/render/gl/passes/name-pass/DebugProgram.ts @@ -6,11 +6,11 @@ */ import flagAtlasMeta from "resources/atlases/flag-atlas-meta.json"; -import type { RenderSettings } from "../../render-settings"; +import type { RenderSettings } from "../../RenderSettings"; import debugBoxFragSrc from "../../shaders/name/debug-box.frag.glsl?raw"; import debugBoxVertSrc from "../../shaders/name/debug-box.vert.glsl?raw"; -import { createProgram } from "../../utils/gl-utils"; -import type { ParsedAtlas } from "./types"; +import { createProgram } from "../../utils/GlUtils"; +import type { ParsedAtlas } from "./Types"; export class DebugProgram { private gl: WebGL2RenderingContext; diff --git a/src/client/render/gl/passes/name-pass/icon-program.ts b/src/client/render/gl/passes/name-pass/IconProgram.ts similarity index 97% rename from src/client/render/gl/passes/name-pass/icon-program.ts rename to src/client/render/gl/passes/name-pass/IconProgram.ts index 869460d346..447522049f 100644 --- a/src/client/render/gl/passes/name-pass/icon-program.ts +++ b/src/client/render/gl/passes/name-pass/IconProgram.ts @@ -8,11 +8,11 @@ import emojiAtlasMeta from "resources/atlases/emoji-atlas-meta.json"; import flagAtlasMeta from "resources/atlases/flag-atlas-meta.json"; import { assetUrl } from "src/core/AssetUrls"; -import type { RenderSettings } from "../../render-settings"; +import type { RenderSettings } from "../../RenderSettings"; import iconFragSrc from "../../shaders/name/icon.frag.glsl?raw"; import iconVertSrc from "../../shaders/name/icon.vert.glsl?raw"; -import { createProgram } from "../../utils/gl-utils"; -import type { ParsedAtlas } from "./types"; +import { createProgram } from "../../utils/GlUtils"; +import type { ParsedAtlas } from "./Types"; const emojiAtlasUrl = assetUrl("atlases/emoji-atlas.png"); const flagAtlasUrl = assetUrl("atlases/flag-atlas.png"); diff --git a/src/client/render/gl/passes/name-pass/status-icon-program.ts b/src/client/render/gl/passes/name-pass/StatusIconProgram.ts similarity index 97% rename from src/client/render/gl/passes/name-pass/status-icon-program.ts rename to src/client/render/gl/passes/name-pass/StatusIconProgram.ts index 40f81392f9..ba071543f9 100644 --- a/src/client/render/gl/passes/name-pass/status-icon-program.ts +++ b/src/client/render/gl/passes/name-pass/StatusIconProgram.ts @@ -11,11 +11,11 @@ import statusAtlasMeta from "resources/atlases/status-atlas-meta.json"; import { assetUrl } from "src/core/AssetUrls"; -import type { RenderSettings } from "../../render-settings"; +import type { RenderSettings } from "../../RenderSettings"; import statusFragSrc from "../../shaders/name/status-icon.frag.glsl?raw"; import statusVertSrc from "../../shaders/name/status-icon.vert.glsl?raw"; -import { createProgram } from "../../utils/gl-utils"; -import type { ParsedAtlas } from "./types"; +import { createProgram } from "../../utils/GlUtils"; +import type { ParsedAtlas } from "./Types"; const statusAtlasUrl = assetUrl("atlases/status-atlas.png"); diff --git a/src/client/render/gl/passes/name-pass/text-layout.ts b/src/client/render/gl/passes/name-pass/TextLayout.ts similarity index 95% rename from src/client/render/gl/passes/name-pass/text-layout.ts rename to src/client/render/gl/passes/name-pass/TextLayout.ts index 5806084bcf..6b41dca4e4 100644 --- a/src/client/render/gl/passes/name-pass/text-layout.ts +++ b/src/client/render/gl/passes/name-pass/TextLayout.ts @@ -3,8 +3,8 @@ * No WebGL dependency. */ -import type { GlyphTables } from "./atlas-data"; -import { CHAR_RANGE, MAX_CHARS } from "./types"; +import type { GlyphTables } from "./AtlasData"; +import { CHAR_RANGE, MAX_CHARS } from "./Types"; export interface LayoutResult { charCodes: Uint8Array; // char code per slot (MAX_CHARS, zero-padded) diff --git a/src/client/render/gl/passes/name-pass/text-program.ts b/src/client/render/gl/passes/name-pass/TextProgram.ts similarity index 96% rename from src/client/render/gl/passes/name-pass/text-program.ts rename to src/client/render/gl/passes/name-pass/TextProgram.ts index 963292f5ae..d76c57e40d 100644 --- a/src/client/render/gl/passes/name-pass/text-program.ts +++ b/src/client/render/gl/passes/name-pass/TextProgram.ts @@ -7,12 +7,12 @@ */ import { assetUrl } from "src/core/AssetUrls"; -import type { RenderSettings } from "../../render-settings"; +import type { RenderSettings } from "../../RenderSettings"; import nameFragSrc from "../../shaders/name/name.frag.glsl?raw"; import nameVertSrc from "../../shaders/name/name.vert.glsl?raw"; -import { createProgram, shaderSrc } from "../../utils/gl-utils"; -import type { ParsedAtlas } from "./types"; -import { LINES_PER_PLAYER, MAX_CHARS } from "./types"; +import { createProgram, shaderSrc } from "../../utils/GlUtils"; +import type { ParsedAtlas } from "./Types"; +import { LINES_PER_PLAYER, MAX_CHARS } from "./Types"; const atlasUrl = assetUrl("atlases/msdf-atlas.png"); diff --git a/src/client/render/gl/passes/name-pass/types.ts b/src/client/render/gl/passes/name-pass/Types.ts similarity index 100% rename from src/client/render/gl/passes/name-pass/types.ts rename to src/client/render/gl/passes/name-pass/Types.ts diff --git a/src/client/render/gl/passes/name-pass/index.ts b/src/client/render/gl/passes/name-pass/index.ts index 3f011452db..10598307c8 100644 --- a/src/client/render/gl/passes/name-pass/index.ts +++ b/src/client/render/gl/passes/name-pass/index.ts @@ -24,30 +24,30 @@ import type { RendererConfig, } from "../../../types"; import { PlayerTypeEnum } from "../../../types"; -import type { RenderSettings } from "../../render-settings"; -import { createFullscreenQuad } from "../../utils/gl-utils"; +import type { RenderSettings } from "../../RenderSettings"; +import { createFullscreenQuad } from "../../utils/GlUtils"; -import type { GlyphTables } from "./atlas-data"; +import type { GlyphTables } from "./AtlasData"; import { buildEmojiLookup, buildFlagLookup, buildGlyphTables, buildKernTable, parseAtlasData, -} from "./atlas-data"; +} from "./AtlasData"; import { buildCursorTex, buildGlyphMetricsTex, buildPlayerDataTex, buildStringTex, -} from "./data-textures"; -import { DebugProgram } from "./debug-program"; -import { IconProgram } from "./icon-program"; -import { StatusIconProgram } from "./status-icon-program"; -import { formatTroops, layoutString } from "./text-layout"; -import { TextProgram } from "./text-program"; -import type { PlayerSlot } from "./types"; -import { LINES_PER_PLAYER, MAX_CHARS } from "./types"; +} from "./DataTextures"; +import { DebugProgram } from "./DebugProgram"; +import { IconProgram } from "./IconProgram"; +import { StatusIconProgram } from "./StatusIconProgram"; +import { formatTroops, layoutString } from "./TextLayout"; +import { TextProgram } from "./TextProgram"; +import type { PlayerSlot } from "./Types"; +import { LINES_PER_PLAYER, MAX_CHARS } from "./Types"; export class NamePass { private gl: WebGL2RenderingContext; diff --git a/src/client/render/gl/utils/affiliation.ts b/src/client/render/gl/utils/Affiliation.ts similarity index 97% rename from src/client/render/gl/utils/affiliation.ts rename to src/client/render/gl/utils/Affiliation.ts index 1dbda6b054..7504567746 100644 --- a/src/client/render/gl/utils/affiliation.ts +++ b/src/client/render/gl/utils/Affiliation.ts @@ -8,8 +8,8 @@ * Rebuilt when localPlayerID or relationship data changes. */ -import { getPaletteSize } from "./color-utils"; -import { createTexture2D } from "./gl-utils"; +import { getPaletteSize } from "./ColorUtils"; +import { createTexture2D } from "./GlUtils"; // Relationship constants (must match adapter.ts) const RELATION_NEUTRAL = 0; diff --git a/src/client/render/gl/utils/color-utils.ts b/src/client/render/gl/utils/ColorUtils.ts similarity index 100% rename from src/client/render/gl/utils/color-utils.ts rename to src/client/render/gl/utils/ColorUtils.ts diff --git a/src/client/render/gl/utils/gl-utils.ts b/src/client/render/gl/utils/GlUtils.ts similarity index 100% rename from src/client/render/gl/utils/gl-utils.ts rename to src/client/render/gl/utils/GlUtils.ts diff --git a/src/client/render/gl/utils/gpu-resources.ts b/src/client/render/gl/utils/GpuResources.ts similarity index 97% rename from src/client/render/gl/utils/gpu-resources.ts rename to src/client/render/gl/utils/GpuResources.ts index 7f86060ba1..9e0c849861 100644 --- a/src/client/render/gl/utils/gpu-resources.ts +++ b/src/client/render/gl/utils/GpuResources.ts @@ -5,7 +5,7 @@ * construction-order dependencies between passes. */ -import { createTexture2D } from "./gl-utils"; +import { createTexture2D } from "./GlUtils"; export interface GPUResources { tileTex: WebGLTexture; // R16UI — tile ownership + flags diff --git a/src/client/render/gl/utils/heat-manager.ts b/src/client/render/gl/utils/HeatManager.ts similarity index 98% rename from src/client/render/gl/utils/heat-manager.ts rename to src/client/render/gl/utils/HeatManager.ts index 0a86230a5e..119175769a 100644 --- a/src/client/render/gl/utils/heat-manager.ts +++ b/src/client/render/gl/utils/HeatManager.ts @@ -8,14 +8,14 @@ * (fallout light reads heat). Shared heat textures come from GPUResources. */ -import type { RenderSettings } from "../render-settings"; +import type { RenderSettings } from "../RenderSettings"; import { createFullscreenQuad, createProgram, createTexture2D, shaderSrc, -} from "./gl-utils"; -import { FALLOUT_BIT, TILE_DEFINES } from "./tile-codec"; +} from "./GlUtils"; +import { FALLOUT_BIT, TILE_DEFINES } from "./TileCodec"; import heatDecayFragSrc from "../shaders/fallout-bloom/heat-decay.frag.glsl?raw"; import fullscreenNoUvVertSrc from "../shaders/shared/fullscreen-no-uv.vert.glsl?raw"; diff --git a/src/client/render/gl/utils/nuke-trajectory.ts b/src/client/render/gl/utils/NukeTrajectory.ts similarity index 100% rename from src/client/render/gl/utils/nuke-trajectory.ts rename to src/client/render/gl/utils/NukeTrajectory.ts diff --git a/src/client/render/gl/utils/tile-codec.ts b/src/client/render/gl/utils/TileCodec.ts similarity index 100% rename from src/client/render/gl/utils/tile-codec.ts rename to src/client/render/gl/utils/TileCodec.ts diff --git a/src/client/render/types/frame-data.ts b/src/client/render/types/FrameData.ts similarity index 97% rename from src/client/render/types/frame-data.ts rename to src/client/render/types/FrameData.ts index 5e2574e60a..04890f591f 100644 --- a/src/client/render/types/frame-data.ts +++ b/src/client/render/types/FrameData.ts @@ -1,4 +1,4 @@ -import type { FrameEvents } from "./frame-events"; +import type { FrameEvents } from "./FrameEvents"; import type { AttackRingInput, NameEntry, @@ -7,7 +7,7 @@ import type { PlayerStatusData, TilePair, UnitState, -} from "./renderer"; +} from "./Renderer"; /** * FrameData — the boundary contract between game integration and features. diff --git a/src/client/render/types/frame-events.ts b/src/client/render/types/FrameEvents.ts similarity index 99% rename from src/client/render/types/frame-events.ts rename to src/client/render/types/FrameEvents.ts index ae5c9c214e..9ae6abb66e 100644 --- a/src/client/render/types/frame-events.ts +++ b/src/client/render/types/FrameEvents.ts @@ -3,7 +3,7 @@ import type { DeadUnitFx, PlayerState, UnitState, -} from "./renderer"; +} from "./Renderer"; // ── Supporting event types ────────────────────────────────────────────── diff --git a/src/client/render/types/frame-source.ts b/src/client/render/types/FrameSource.ts similarity index 92% rename from src/client/render/types/frame-source.ts rename to src/client/render/types/FrameSource.ts index 74f6f0217c..b2a2a4e50e 100644 --- a/src/client/render/types/frame-source.ts +++ b/src/client/render/types/FrameSource.ts @@ -1,5 +1,5 @@ -import type { FrameData } from "./frame-data"; -import type { PlayerStatic } from "./renderer"; +import type { FrameData } from "./FrameData"; +import type { PlayerStatic } from "./Renderer"; /** * Static per-session metadata. Set once at game-start, never changes. diff --git a/src/client/render/types/game.ts b/src/client/render/types/Game.ts similarity index 100% rename from src/client/render/types/game.ts rename to src/client/render/types/Game.ts diff --git a/src/client/render/types/game-updates.ts b/src/client/render/types/GameUpdates.ts similarity index 100% rename from src/client/render/types/game-updates.ts rename to src/client/render/types/GameUpdates.ts diff --git a/src/client/render/types/renderer.ts b/src/client/render/types/Renderer.ts similarity index 100% rename from src/client/render/types/renderer.ts rename to src/client/render/types/Renderer.ts diff --git a/src/client/render/types/replay.ts b/src/client/render/types/Replay.ts similarity index 99% rename from src/client/render/types/replay.ts rename to src/client/render/types/Replay.ts index c02ccc2831..a6018a78c3 100644 --- a/src/client/render/types/replay.ts +++ b/src/client/render/types/Replay.ts @@ -7,7 +7,7 @@ import type { RendererConfig, TilePair, UnitState, -} from "./renderer"; +} from "./Renderer"; /** Chunk index entry — one per chunk in the file */ export interface ChunkIndexEntry { diff --git a/src/client/render/types/unit-type.ts b/src/client/render/types/UnitType.ts similarity index 100% rename from src/client/render/types/unit-type.ts rename to src/client/render/types/UnitType.ts diff --git a/src/client/render/types/index.ts b/src/client/render/types/index.ts index 8785374f34..ff724aa0a5 100644 --- a/src/client/render/types/index.ts +++ b/src/client/render/types/index.ts @@ -1,5 +1,5 @@ // Renderer types (units, players, tiles, names, config) -export { PlayerTypeEnum, TrainType } from "./renderer"; +export { PlayerTypeEnum, TrainType } from "./Renderer"; export type { AllianceData, AttackData, @@ -17,13 +17,13 @@ export type { RendererConfig, TilePair, UnitState, -} from "./renderer"; +} from "./Renderer"; // Frame data — boundary contract between game integration and features -export type { FrameData } from "./frame-data"; +export type { FrameData } from "./FrameData"; // Frame events — per-frame ephemeral events (rendering FX + stats events) -export { EMPTY_FRAME_EVENTS } from "./frame-events"; +export { EMPTY_FRAME_EVENTS } from "./FrameEvents"; export type { AllianceBrokenEvent, AllianceExpiredEvent, @@ -36,13 +36,13 @@ export type { NukeIncomingEvent, TargetEvent, WinEvent, -} from "./frame-events"; +} from "./FrameEvents"; // Frame source — mode-agnostic subscription interface -export type { FrameSource, GameStartConfig } from "./frame-source"; +export type { FrameSource, GameStartConfig } from "./FrameSource"; // Game update types -export type { GameStartInfo, GameUpdateViewData } from "./game"; +export type { GameStartInfo, GameUpdateViewData } from "./Game"; // Replay types (header, frames, codec helpers) export type { @@ -58,10 +58,10 @@ export type { ReplayHeader, StreamableReplayInfo, TrainPlanRecord, -} from "./replay"; +} from "./Replay"; // Game update type constants and event payloads (shared between shim + codec) -export { GameUpdateType, MessageType } from "./game-updates"; +export { GameUpdateType, MessageType } from "./GameUpdates"; export type { AllianceExpiredUpdate, AllianceReplyUpdate, @@ -81,7 +81,7 @@ export type { UnitEventUpdate, UnitIncomingUpdate, WinUpdate, -} from "./game-updates"; +} from "./GameUpdates"; // Unit type string constants and derived sets export { @@ -105,4 +105,4 @@ export { UT_TRAIN, UT_TRANSPORT, UT_WARSHIP, -} from "./unit-type"; +} from "./UnitType"; diff --git a/src/client/view/GameView.ts b/src/client/view/GameView.ts index 4daf46d8ba..2832673f09 100644 --- a/src/client/view/GameView.ts +++ b/src/client/view/GameView.ts @@ -26,13 +26,13 @@ import { UnitGrid, UnitPredicate } from "../../core/game/UnitGrid"; import { ClientID, GameID, Player, PlayerCosmetics } from "../../core/Schemas"; import { formatPlayerDisplayName } from "../../core/Util"; import { WorkerClient } from "../../core/worker/WorkerClient"; -import { computeAllianceClusters } from "../render/frame/derive/alliance-clusters"; -import { extractAttackRings } from "../render/frame/derive/attack-rings"; -import { extractNukeTelegraphs } from "../render/frame/derive/nuke-telegraphs"; -import { computePlayerStatus } from "../render/frame/derive/player-status"; -import { buildRelationMatrix } from "../render/frame/derive/relation-matrix"; -import { RailroadCache } from "../render/frame/railroad-cache"; -import { TrailManager } from "../render/frame/trail-manager"; +import { computeAllianceClusters } from "../render/frame/derive/AllianceClusters"; +import { extractAttackRings } from "../render/frame/derive/AttackRings"; +import { extractNukeTelegraphs } from "../render/frame/derive/NukeTelegraphs"; +import { computePlayerStatus } from "../render/frame/derive/PlayerStatus"; +import { buildRelationMatrix } from "../render/frame/derive/RelationMatrix"; +import { RailroadCache } from "../render/frame/RailroadCache"; +import { TrailManager } from "../render/frame/TrailManager"; import type { FrameData, NameEntry, TilePair } from "../render/types"; import { STRUCTURE_TYPES } from "../render/types"; import { PlayerView } from "./PlayerView"; diff --git a/tests/client/render/frame/derive/player-status.test.ts b/tests/client/render/frame/derive/player-status.test.ts index e511555b9b..16a4b7ed83 100644 --- a/tests/client/render/frame/derive/player-status.test.ts +++ b/tests/client/render/frame/derive/player-status.test.ts @@ -12,7 +12,7 @@ */ import { describe, expect, it } from "vitest"; -import { computePlayerStatus } from "../../../../../src/client/render/frame/derive/player-status"; +import { computePlayerStatus } from "../../../../../src/client/render/frame/derive/PlayerStatus"; import type { PlayerState, UnitState, From 61f6d2fdd4366d280c8a079f682fc26a1cbb3e66 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Mon, 18 May 2026 09:10:05 -0700 Subject: [PATCH 33/34] restore alt-view (space hold) toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InputHandler still emits AlternateViewEvent on space down/up, and the renderer still has setAltView. The bridge between them lived in MapInteraction's applyAltView, which got deleted with the rest of MapInteraction — nothing was wiring the event to the view anymore. Expose view.setAltView and have ClientGameRunner subscribe. --- src/client/ClientGameRunner.ts | 6 ++++++ src/client/render/gl/GameView.ts | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index db807385f1..0bae6d0f2d 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -37,6 +37,7 @@ import { import { WorkerClient } from "../core/worker/WorkerClient"; import { getPersistentID } from "./Auth"; import { + AlternateViewEvent, AutoUpgradeEvent, DoBoatAttackEvent, DoBreakAllianceEvent, @@ -445,6 +446,11 @@ async function createClientGame( (e) => applyDayNightMode((e as CustomEvent).detail === "true"), ); + // Space-hold (and the settings-modal toggle) drives the affiliation + // recolor. InputHandler emits AlternateViewEvent; the WebGL view needs + // setAltView called to switch passes into alt mode. + eventBus.on(AlternateViewEvent, (e) => view.setAltView(e.alternateView)); + const gameRenderer = createRenderer( inputOverlay, gameView, diff --git a/src/client/render/gl/GameView.ts b/src/client/render/gl/GameView.ts index d0f68b4faa..8108514e58 100644 --- a/src/client/render/gl/GameView.ts +++ b/src/client/render/gl/GameView.ts @@ -321,6 +321,9 @@ export class GameView { setLocalPlayerID(id: number): void { this.renderer.setLocalPlayerID(id); } + setAltView(active: boolean): void { + this.renderer.setAltView(active); + } setHighlightOwner(ownerID: number): void { this.renderer.setHighlightOwner(ownerID); } From 4936ae3d59854c786c42b4c863bbc54d1a50fb4e Mon Sep 17 00:00:00 2001 From: evanpelle Date: Mon, 18 May 2026 09:43:14 -0700 Subject: [PATCH 34/34] restore spawn-phase glow with a true breathing animation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SpawnOverlayPass had everything wired except a caller. WebGLFrameBuilder now collects spawned human players each tick during spawn phase and pushes their territory centroid + color through view.updateSpawnOverlay. myPlayer reads as white so the local-player ring stands out. Reshaped the shader animation: dropped the growing-disc effect, gave the ring a true breath — radius scales 0.5×→1.15× while opacity pulses 35%→100% in phase. Replaced the sharp inner-edge ramp with a smooth center-to-boundary fill so there's no hard cutoff or empty hole in the middle. animSpeed bumped to 0.0035 (~1 breath/sec). --- src/client/WebGLFrameBuilder.ts | 47 +++++++++++++++++- src/client/render/gl/render-settings.json | 2 +- .../spawn-overlay/spawn-overlay.frag.glsl | 48 +++++++++---------- 3 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/client/WebGLFrameBuilder.ts b/src/client/WebGLFrameBuilder.ts index 065fc4ebba..99bb15d402 100644 --- a/src/client/WebGLFrameBuilder.ts +++ b/src/client/WebGLFrameBuilder.ts @@ -1,7 +1,12 @@ import { Colord } from "colord"; +import { PlayerType } from "../core/game/Game"; import { GameView } from "../core/game/GameView"; import { uploadFrameData } from "./render/frame/Upload"; -import { PlayerStatic, GameView as WebGLGameView } from "./render/gl"; +import { + PlayerStatic, + SpawnCenter, + GameView as WebGLGameView, +} from "./render/gl"; const PALETTE_SIZE = 4096; @@ -31,6 +36,7 @@ export class WebGLFrameBuilder { update(gameView: GameView): void { this.syncPlayers(gameView); this.syncLocalPlayer(gameView); + this.syncSpawnOverlay(gameView); uploadFrameData(this.view, gameView.frameData()); } @@ -41,6 +47,45 @@ export class WebGLFrameBuilder { this.view.setLocalPlayerID(sid); } + /** + * Spawn-phase highlights: each already-spawned human player gets a colored + * ring + tile glow around their starting territory. Pushed every tick + * during spawn phase; the pass animates locally from the snapshot. + */ + private syncSpawnOverlay(gameView: GameView): void { + const inSpawnPhase = gameView.inSpawnPhase(); + if (!inSpawnPhase) { + this.view.updateSpawnOverlay(false, []); + return; + } + const me = gameView.myPlayer(); + const myTeam = me?.team() ?? null; + const centers: SpawnCenter[] = []; + for (const p of gameView.players()) { + if (!p.isPlayer() || p.type() !== PlayerType.Human) continue; + if (!p.hasSpawned()) continue; + const isSelf = me !== null && p.smallID() === me.smallID(); + // myPlayer reads as plain white so the local-player ring is visually + // distinct from any team color; everyone else uses their territory tint. + const c = isSelf + ? { r: 255, g: 255, b: 255 } + : p.territoryColor().toRgb(); + centers.push({ + x: p.nameData?.x ?? 0, + y: p.nameData?.y ?? 0, + r: c.r / 255, + g: c.g / 255, + b: c.b / 255, + isSelf, + isTeammate: + myTeam !== null && + p.team() === myTeam && + p.smallID() !== me?.smallID(), + }); + } + this.view.updateSpawnOverlay(true, centers); + } + private syncPlayers(gameView: GameView): void { const newPlayers: PlayerStatic[] = []; for (const p of gameView.players()) { diff --git a/src/client/render/gl/render-settings.json b/src/client/render/gl/render-settings.json index e7d2058ecf..e51eaef0f3 100644 --- a/src/client/render/gl/render-settings.json +++ b/src/client/render/gl/render-settings.json @@ -245,7 +245,7 @@ "selfMaxRad": 24, "mateMinRad": 5, "mateMaxRad": 14, - "animSpeed": 0.005, + "animSpeed": 0.0035, "gradientInnerEdge": 0.01, "gradientSolidEnd": 0.1 }, diff --git a/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl b/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl index fd0578d822..8e116ebd2a 100644 --- a/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl +++ b/src/client/render/gl/shaders/spawn-overlay/spawn-overlay.frag.glsl @@ -67,34 +67,32 @@ void main() { continue; } - // Static outer ring: radial gradient from minR to maxR - float range = maxR - minR; - float t = (dist - minR) / range; - if (t > 0.0 && t <= 1.0) { - float innerEdge = uGradientStops.x; - float solidEnd = uGradientStops.y; - float alpha; - if (t < innerEdge) { - alpha = t / innerEdge; - } else if (t < solidEnd) { - alpha = 1.0; - } else { - alpha = 1.0 - (t - solidEnd) / (1.0 - solidEnd); - } - + // Breathing ring: the gradient halo shrinks/expands in radius AND its + // opacity pulses in phase with the breath — both driven by uBreathRadius. + // Smooth bell shape: glow ramps up from center to the inner edge, stays + // solid through the ring's body, then fades out past solidEnd. No hard + // cutoffs at either side. + float scale = 0.5 + 0.65 * uBreathRadius; // 0.5 → 1.15 of base radius + float bMinR = minR * scale; + float bMaxR = maxR * scale; + float range = bMaxR - bMinR; + float t = (dist - bMinR) / range; + float solidEnd = uGradientStops.y; + float alpha = 0.0; + if (dist < bMinR) { + // Inner glow: linear ramp from 0 at center to 1 at the ring's inner edge. + alpha = dist / max(bMinR, 0.001); + } else if (t < solidEnd) { + alpha = 1.0; + } else if (t < 1.0) { + alpha = 1.0 - (t - solidEnd) / (1.0 - solidEnd); + } + if (alpha > 0.0) { + // Opacity pulses 35% → 100% in phase with the radius. + alpha *= 0.35 + 0.65 * uBreathRadius; result.rgb = mix(result.rgb, color, alpha * (1.0 - result.a)); result.a = result.a + alpha * (1.0 - result.a); } - - // Breathing ring: solid colored disc from minR to breathR - float breathR = minR + range * uBreathRadius; - if (breathR > minR + 0.01) { - if (dist >= minR && dist <= breathR) { - float edge = smoothstep(minR, minR + 0.1, dist); - result.rgb = color; - result.a = max(result.a, edge); - } - } } if (result.a < 0.001) discard;