From e5c7ba24bcc51a9d41af7a52b6f064eabef1e15c Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:10:36 +0100 Subject: [PATCH] border test 9000 --- resources/lang/en.json | 2 + src/client/UserSettingModal.ts | 26 ++++++++ .../baseComponents/setting/SettingSelect.ts | 62 +++++++++++++++++++ src/client/graphics/layers/SettingsModal.ts | 39 ++++++++++++ src/client/graphics/layers/TerritoryLayer.ts | 15 +++++ .../graphics/webgpu/TerritoryRenderer.ts | 7 +++ .../graphics/webgpu/core/GroundTruthData.ts | 13 +++- .../webgpu/shaders/render/territory.wgsl | 59 +++++++++++++++++- src/core/game/UserSettings.ts | 18 ++++++ 9 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 src/client/components/baseComponents/setting/SettingSelect.ts diff --git a/resources/lang/en.json b/resources/lang/en.json index 2ae02389d..279ae5205 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -466,6 +466,8 @@ "attack_ratio_desc": "What percentage of your troops to send in an attack (1–100%)", "territory_patterns_label": "🏳️ Territory Skins", "territory_patterns_desc": "Choose whether to display territory skin designs in game", + "territory_border_mode_label": "Territory Borders", + "territory_border_mode_desc": "Select border rendering style (visual only)", "performance_overlay_label": "Performance Overlay", "performance_overlay_desc": "Toggle the performance overlay. When enabled, the performance overlay will be displayed. Press shift-D during game to toggle.", "easter_writing_speed_label": "Writing Speed Multiplier", diff --git a/src/client/UserSettingModal.ts b/src/client/UserSettingModal.ts index 70f4251f8..0ada4565d 100644 --- a/src/client/UserSettingModal.ts +++ b/src/client/UserSettingModal.ts @@ -5,6 +5,7 @@ import { UserSettings } from "../core/game/UserSettings"; import "./components/baseComponents/setting/SettingKeybind"; import { SettingKeybind } from "./components/baseComponents/setting/SettingKeybind"; import "./components/baseComponents/setting/SettingNumber"; +import "./components/baseComponents/setting/SettingSelect"; import "./components/baseComponents/setting/SettingSlider"; import "./components/baseComponents/setting/SettingToggle"; import { BaseModal } from "./components/BaseModal"; @@ -370,6 +371,16 @@ export class UserSettingModal extends BaseModal { console.log("🏳️ Territory Patterns:", enabled ? "ON" : "OFF"); } + private changeTerritoryBorderMode(e: CustomEvent<{ value: string }>) { + const value = e.detail?.value; + if (typeof value !== "string") return; + + const mode = parseInt(value, 10); + if (!Number.isFinite(mode)) return; + + this.userSettings.setInt("settings.territoryBorderMode", mode); + } + private togglePerformanceOverlay(e: CustomEvent<{ checked: boolean }>) { const enabled = e.detail?.checked; if (typeof enabled !== "boolean") return; @@ -794,6 +805,21 @@ export class UserSettingModal extends BaseModal { this.toggleDarkMode(e)} > + + + + + ${this.label} + + + ${this.description} + + + + + ${this.options.map( + (o) => html`${o.label}`, + )} + + + `; + } +} diff --git a/src/client/graphics/layers/SettingsModal.ts b/src/client/graphics/layers/SettingsModal.ts index 92d8f1fe4..ee29599b9 100644 --- a/src/client/graphics/layers/SettingsModal.ts +++ b/src/client/graphics/layers/SettingsModal.ts @@ -141,6 +141,17 @@ export class SettingsModal extends LitElement implements Layer { this.requestUpdate(); } + private onTerritoryBorderModeChange(event: Event) { + const value = (event.target as HTMLSelectElement).value; + const mode = Number.parseInt(value, 10); + if (!Number.isFinite(mode)) + throw new Error(`Invalid border mode: ${value}`); + + this.userSettings.setInt("settings.territoryBorderMode", mode); + this.eventBus.emit(new RefreshGraphicsEvent()); + this.requestUpdate(); + } + private onToggleRandomNameModeButtonClick() { this.userSettings.toggleRandomName(); this.requestUpdate(); @@ -286,6 +297,34 @@ export class SettingsModal extends LitElement implements Layer { + + + + + ${translateText("user_setting.territory_border_mode_label")} + + + ${translateText("user_setting.territory_border_mode_desc")} + + + + Off + Simple + Glow + + + u: Uniforms; @@ -30,6 +30,7 @@ fn fsMain(@builtin(position) pos: vec4f) -> @location(0) vec4f { let altView = u.viewOffset_alt_highlight.z; let highlightId = u.viewOffset_alt_highlight.w; let viewSize = u.viewSize_pad.xy; + let borderMode = u.viewSize_pad.z; // WebGPU fragment position is top-left origin and at pixel centers (0.5, 1.5, ...). let viewCoord = vec2f(pos.x - 0.5, pos.y - 0.5); @@ -76,6 +77,62 @@ fn fsMain(@builtin(position) pos: vec4f) -> @location(0) vec4f { outColor = terrain; } + // Borders (purely visual): render a stable-pixel-width line at ownership edges. + if (borderMode > 0.5 && altView <= 0.5 && owner != 0u) { + let fx = fract(mapCoord.x); + let fy = fract(mapCoord.y); + + var dist = 1e9; + + // Only border against other non-zero owners. + if (texCoord.x > 0) { + let o = textureLoad(stateTex, texCoord + vec2i(-1, 0), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, fx); + } + } + if (texCoord.x + 1 < i32(mapRes.x)) { + let o = textureLoad(stateTex, texCoord + vec2i(1, 0), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, 1.0 - fx); + } + } + if (texCoord.y > 0) { + let o = textureLoad(stateTex, texCoord + vec2i(0, -1), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, fy); + } + } + if (texCoord.y + 1 < i32(mapRes.y)) { + let o = textureLoad(stateTex, texCoord + vec2i(0, 1), 0).x & 0xFFFu; + if (o != 0u && o != owner) { + dist = min(dist, 1.0 - fy); + } + } + + if (dist < 1e8) { + let pxPerTile = max(viewScale, 0.001); + let aaTiles = 1.0 / pxPerTile; + let thicknessPx = select(1.0, 2.5, borderMode > 1.5); + let thicknessTiles = thicknessPx / pxPerTile; + + let line = 1.0 - smoothstep(thicknessTiles, thicknessTiles + aaTiles, dist); + outColor = vec4f( + mix(outColor.rgb, vec3f(0.0, 0.0, 0.0), clamp(line * 0.9, 0.0, 0.9)), + outColor.a, + ); + + if (borderMode > 1.5) { + let glowTiles = (thicknessPx * 3.0) / pxPerTile; + let glow = 1.0 - smoothstep(glowTiles, glowTiles + aaTiles * 2.0, dist); + outColor = vec4f( + mix(outColor.rgb, vec3f(1.0, 1.0, 1.0), clamp(glow * 0.12, 0.0, 0.12)), + outColor.a, + ); + } + } + } + // Apply hover highlight if needed if (highlightId > 0.5) { let alpha = select(0.65, 0.0, altView > 0.5); diff --git a/src/core/game/UserSettings.ts b/src/core/game/UserSettings.ts index ba74b9ae8..74796e151 100644 --- a/src/core/game/UserSettings.ts +++ b/src/core/game/UserSettings.ts @@ -33,6 +33,20 @@ export class UserSettings { localStorage.setItem(key, value.toString()); } + getInt(key: string, defaultValue: number): number { + const value = localStorage.getItem(key); + if (!value) return defaultValue; + + const intValue = parseInt(value, 10); + if (!Number.isFinite(intValue)) return defaultValue; + + return intValue; + } + + setInt(key: string, value: number): void { + localStorage.setItem(key, Math.trunc(value).toString()); + } + emojis() { return this.get("settings.emojis", true); } @@ -73,6 +87,10 @@ export class UserSettings { return this.get("settings.territoryPatterns", true); } + territoryBorderMode(): number { + return this.getInt("settings.territoryBorderMode", 1); + } + cursorCostLabel() { const legacy = this.get("settings.ghostPricePill", true); return this.get("settings.cursorCostLabel", legacy);