diff --git a/src/config/normalize-runtime-settings.test.ts b/src/config/normalize-runtime-settings.test.ts deleted file mode 100644 index 259ff17..0000000 --- a/src/config/normalize-runtime-settings.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { - normalizeNumberControlValue, - normalizeRuntimeSettings, -} from './normalize-runtime-settings'; -import type { GardenRuntimeSettings } from './types'; - -describe('normalizeNumberControlValue', () => { - it('clamps and rounds numeric controls', () => { - expect( - normalizeNumberControlValue(12.6, { - folder: 'Test', - integer: true, - max: 10, - min: 0, - }) - ).toBe(10); - - expect( - normalizeNumberControlValue(Number.NaN, { - folder: 'Test', - min: 3, - }) - ).toBe(3); - }); - - it('keeps only declared option values', () => { - expect( - normalizeNumberControlValue(2, { - folder: 'Test', - options: { off: 0, on: 2 }, - }) - ).toBe(2); - - expect( - normalizeNumberControlValue(3, { - folder: 'Test', - options: { off: 0, on: 2 }, - }) - ).toBe(0); - }); -}); - -describe('normalizeRuntimeSettings', () => { - it('normalizes configured runtime keys and leaves hidden keys alone', () => { - const settings = { - brushSize: 99, - selectedColorIndex: 7, - } as GardenRuntimeSettings; - - expect( - normalizeRuntimeSettings(settings, { - brushSize: { - folder: 'Brush', - max: 12, - min: 1, - }, - }) - ).toMatchObject({ - brushSize: 12, - selectedColorIndex: 7, - }); - }); -}); diff --git a/src/config/runtime-controls.ts b/src/config/runtime-controls.ts index e6b8a70..e07a259 100644 --- a/src/config/runtime-controls.ts +++ b/src/config/runtime-controls.ts @@ -67,7 +67,7 @@ export const runtimeControls: GardenAppConfig['runtimeSettings']['controls'] = { folder: 'Movement', label: 'Travel Speed', min: 10, - max: 250, + max: 500, step: 1, }, turnSpeed: { @@ -116,7 +116,6 @@ export const runtimeControls: GardenAppConfig['runtimeSettings']['controls'] = { clarity: { folder: 'Look', - inverted: true, label: 'Sharpness', min: 0.00001, max: 1, diff --git a/src/config/runtime-setting-bounds.ts b/src/config/runtime-setting-bounds.ts deleted file mode 100644 index 5400912..0000000 --- a/src/config/runtime-setting-bounds.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const INTERNAL_RENDER_AREA_MEGAPIXEL_LIMITS = { - min: 0.5, - max: 16.6, -} as const; diff --git a/src/config/types.ts b/src/config/types.ts index 6f9aa29..456dec8 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -12,7 +12,6 @@ export interface NumberControlConfig { format?: (value: number) => string; folder: string; integer?: boolean; - inverted?: boolean; label?: string; max?: number; min?: number; diff --git a/src/page/color-reaction-matrix-control.ts b/src/page/color-reaction-matrix-control.ts deleted file mode 100644 index ca78cbb..0000000 --- a/src/page/color-reaction-matrix-control.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { FolderApi } from '@tweakpane/core'; - -import { appConfig, normalizeNumberControlValue } from '../config'; -import { activeVibe, settings } from '../settings'; -import { rgbColorToCss } from '../utils/rgb-color'; - -type PaneContainer = Pick; -type ColorReactionKey = (typeof colorReactionRows)[number]['keys'][number]; - -const COLOR_REACTION_LABELS = ['Color 1', 'Color 2', 'Color 3'] as const; -const COLOR_REACTION_STATES = [ - { id: 'follow', label: 'Move Toward', value: 1 }, - { id: 'ignore', label: 'Ignore', value: 0 }, - { id: 'avoid', label: 'Move Away', value: -1 }, -] as const; - -const colorReactionRows = [ - { - colorIndex: 0, - label: COLOR_REACTION_LABELS[0], - keys: ['color1ToColor1', 'color1ToColor2', 'color1ToColor3'], - }, - { - colorIndex: 1, - label: COLOR_REACTION_LABELS[1], - keys: ['color2ToColor1', 'color2ToColor2', 'color2ToColor3'], - }, - { - colorIndex: 2, - label: COLOR_REACTION_LABELS[2], - keys: ['color3ToColor1', 'color3ToColor2', 'color3ToColor3'], - }, -] as const; - -const getColorReactionStateIndex = (value: number): number => - COLOR_REACTION_STATES.findIndex((state) => state.value === value); - -const getColorReactionState = (value: number): (typeof COLOR_REACTION_STATES)[number] => - COLOR_REACTION_STATES[getColorReactionStateIndex(value)] ?? COLOR_REACTION_STATES[1]; - -const getNextColorReactionState = ( - value: number -): (typeof COLOR_REACTION_STATES)[number] => { - const index = getColorReactionStateIndex(value); - return COLOR_REACTION_STATES[ - ((index < 0 ? 1 : index) + 1) % COLOR_REACTION_STATES.length - ]; -}; - -export class ColorReactionMatrixControl { - private readonly buttons = new Map< - ColorReactionKey, - { - element: HTMLButtonElement; - sourceColorIndex: number; - targetColorIndex: number; - } - >(); - private readonly swatches: Array<{ - colorIndex: number; - element: HTMLElement; - }> = []; - - public constructor(private readonly onRuntimeChange: () => void) {} - - public addTo(container: PaneContainer): void { - const folder = container.addFolder({ - title: 'Color Behavior', - expanded: true, - }); - - const matrix = document.createElement('div'); - matrix.className = 'color-reaction-matrix'; - - matrix.appendChild(this.createCorner()); - colorReactionRows.forEach((row) => { - matrix.appendChild(this.createHeader(row.colorIndex, row.label)); - }); - - colorReactionRows.forEach((row) => { - matrix.appendChild(this.createHeader(row.colorIndex, row.label)); - row.keys.forEach((key, columnIndex) => { - matrix.appendChild(this.createCell(key, row.colorIndex, columnIndex)); - }); - }); - - const matrixBlade = folder.addBlade({ view: 'separator' }); - matrixBlade.element.classList.add('color-reaction-matrix-blade'); - matrixBlade.element.replaceChildren(matrix); - this.sync(); - } - - public sync(): void { - this.buttons.forEach(({ element, sourceColorIndex, targetColorIndex }, key) => { - this.syncButton(element, key, sourceColorIndex, targetColorIndex); - }); - - this.swatches.forEach(({ colorIndex, element }) => { - element.style.backgroundColor = rgbColorToCss(activeVibe.colors[colorIndex]); - }); - } - - private createCorner(): HTMLDivElement { - const corner = document.createElement('div'); - corner.className = 'color-reaction-matrix__corner'; - return corner; - } - - private createHeader(colorIndex: number, label: string): HTMLDivElement { - const header = document.createElement('div'); - header.className = 'color-reaction-matrix__header'; - header.setAttribute('aria-label', label); - header.title = label; - - const swatch = document.createElement('span'); - swatch.className = 'color-reaction-matrix__swatch'; - this.swatches.push({ colorIndex, element: swatch }); - header.appendChild(swatch); - - return header; - } - - private createCell( - key: ColorReactionKey, - sourceColorIndex: number, - targetColorIndex: number - ): HTMLDivElement { - const cell = document.createElement('div'); - cell.className = 'color-reaction-matrix__cell'; - - const config = appConfig.runtimeSettings.controls[key]; - if (!config) { - return cell; - } - - const button = document.createElement('button'); - button.className = 'color-reaction-matrix__button'; - button.type = 'button'; - - const icon = document.createElement('span'); - icon.className = 'color-reaction-matrix__icon'; - button.appendChild(icon); - - button.addEventListener('click', () => { - const currentValue = normalizeNumberControlValue(settings[key], config); - const nextState = getNextColorReactionState(currentValue); - settings[key] = nextState.value; - this.syncButton(button, key, sourceColorIndex, targetColorIndex); - this.onRuntimeChange(); - }); - - this.buttons.set(key, { - element: button, - sourceColorIndex, - targetColorIndex, - }); - cell.appendChild(button); - - return cell; - } - - private syncButton( - button: HTMLButtonElement, - key: ColorReactionKey, - sourceColorIndex: number, - targetColorIndex: number - ): void { - const config = appConfig.runtimeSettings.controls[key]; - if (!config) { - return; - } - - settings[key] = normalizeNumberControlValue(settings[key], config); - - const state = getColorReactionState(settings[key]); - const nextState = getNextColorReactionState(settings[key]); - const sourceLabel = COLOR_REACTION_LABELS[sourceColorIndex]; - const targetLabel = COLOR_REACTION_LABELS[targetColorIndex]; - - button.dataset.reaction = state.id; - button.setAttribute( - 'aria-label', - `${sourceLabel} ${state.label.toLowerCase()} ${targetLabel.toLowerCase()} trails; click to switch to ${nextState.label.toLowerCase()}` - ); - button.title = `${sourceLabel}: ${state.label} ${targetLabel} trails`; - } -} diff --git a/src/page/config-pane.ts b/src/page/config-pane.ts index 8bb7f28..3e6c397 100644 --- a/src/page/config-pane.ts +++ b/src/page/config-pane.ts @@ -81,16 +81,6 @@ const getNumberBindingParams = (config: NumberControlConfig): BindingParams => { return params; }; -const getInvertedNumberControlValue = ( - value: number, - config: NumberControlConfig -): number => { - if (!config.inverted || config.min === undefined || config.max === undefined) { - return value; - } - return config.min + config.max - value; -}; - export class ConfigPane { private readonly container: HTMLDivElement; private readonly closeButton: HTMLButtonElement; @@ -274,10 +264,9 @@ export class ConfigPane { } settings[key] = normalizeNumberControlValue(settings[key], config); - const bindingTarget = this.getRuntimeBindingTarget(key, config); container - .addBinding(bindingTarget, key, getNumberBindingParams(config)) + .addBinding(settings, key, getNumberBindingParams(config)) .on('change', () => { const nextValue = normalizeNumberControlValue(settings[key], config); if (nextValue !== settings[key]) { @@ -288,25 +277,6 @@ export class ConfigPane { }); } - private getRuntimeBindingTarget( - key: RuntimeControlKey, - config: NumberControlConfig - ): typeof settings | Record { - if (!config.inverted) { - return settings; - } - - const bindingTarget = {} as Record; - Object.defineProperty(bindingTarget, key, { - enumerable: true, - get: () => getInvertedNumberControlValue(settings[key], config), - set: (value: number) => { - settings[key] = getInvertedNumberControlValue(value, config); - }, - }); - return bindingTarget; - } - private getRuntimeControlConfig( key: RuntimeControlKey ): NumberControlConfig | undefined { diff --git a/src/page/eraser-size-control.test.ts b/src/page/eraser-size-control.test.ts deleted file mode 100644 index 6b2bbe8..0000000 --- a/src/page/eraser-size-control.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { appConfig } from '../config'; -import { - getEraserSizeFromSliderRatio, - getEraserSliderRatioFromSize, -} from './eraser-size-control'; - -describe('eraser size slider mapping', () => { - it('maps slider position quadratically to eraser size', () => { - const { max, min } = appConfig.toolbar.eraser; - - expect(getEraserSizeFromSliderRatio(0)).toBe(min); - expect(getEraserSizeFromSliderRatio(0.5)).toBe(min + (max - min) * 0.25); - expect(getEraserSizeFromSliderRatio(1)).toBe(max); - }); - - it('maps eraser size back to the inverse slider position', () => { - const { max, min } = appConfig.toolbar.eraser; - const quarterRangeSize = min + (max - min) * 0.25; - - expect(getEraserSliderRatioFromSize(min)).toBe(0); - expect(getEraserSliderRatioFromSize(quarterRangeSize)).toBe(0.5); - expect(getEraserSliderRatioFromSize(max)).toBe(1); - }); -}); diff --git a/src/page/eraser-size-control.ts b/src/page/eraser-size-control.ts index 2c0e8a8..1099821 100644 --- a/src/page/eraser-size-control.ts +++ b/src/page/eraser-size-control.ts @@ -9,28 +9,11 @@ const clampEraserSize = (value: number): number => { return Math.min(max, Math.max(min, Math.round(safeValue))); }; -const ERASER_SLIDER_MIN = 0; -const ERASER_SLIDER_MAX = 1; -const ERASER_SLIDER_STEP = 0.001; - -const clampSliderRatio = (value: number): number => { - const safeValue = Number.isFinite(value) ? value : ERASER_SLIDER_MIN; - return Math.min(ERASER_SLIDER_MAX, Math.max(ERASER_SLIDER_MIN, safeValue)); -}; - const getEraserSizeRatio = (size: number): number => { const { max, min } = appConfig.toolbar.eraser; - return (clampEraserSize(size) - min) / (max - min); + return (size - min) / (max - min); }; -export const getEraserSizeFromSliderRatio = (sliderRatio: number): number => { - const { max, min } = appConfig.toolbar.eraser; - return clampEraserSize(min + (max - min) * clampSliderRatio(sliderRatio) ** 2); -}; - -export const getEraserSliderRatioFromSize = (size: number): number => - Math.sqrt(getEraserSizeRatio(size)); - interface EraserSizeControlOptions { getGame: () => GameLoop | null; onActivate: () => void; @@ -50,7 +33,7 @@ export class EraserSizeControl { this.control.addEventListener('click', this.activate); this.slider.addEventListener('focus', this.activate); this.slider.addEventListener('input', () => { - settings.eraserSize = getEraserSizeFromSliderRatio(Number(this.slider.value)); + settings.eraserSize = clampEraserSize(Number(this.slider.value)); this.activate(); this.render(); this.options.onChange(); @@ -63,20 +46,19 @@ export class EraserSizeControl { settings.eraserSize = size; } - const sliderRatio = getEraserSliderRatioFromSize(size); - this.slider.min = ERASER_SLIDER_MIN.toString(); - this.slider.max = ERASER_SLIDER_MAX.toString(); - this.slider.step = ERASER_SLIDER_STEP.toString(); - this.slider.value = sliderRatio.toString(); + this.slider.min = appConfig.toolbar.eraser.min.toString(); + this.slider.max = appConfig.toolbar.eraser.max.toString(); + this.slider.step = appConfig.toolbar.eraser.step.toString(); + this.slider.value = size.toString(); this.slider.setAttribute('aria-valuetext', `${size}px`); - const sizeRatio = getEraserSizeRatio(size); + const ratio = getEraserSizeRatio(size); const scale = appConfig.toolbar.eraser.controlScaleMin + (appConfig.toolbar.eraser.controlScaleMax - appConfig.toolbar.eraser.controlScaleMin) * - sizeRatio; - this.control.style.setProperty('--eraser-progress', `${sliderRatio * 100}%`); + ratio; + this.control.style.setProperty('--eraser-progress', `${ratio * 100}%`); this.control.style.setProperty('--eraser-control-scale', scale.toFixed(3)); this.syncActiveState(); this.options.getGame()?.updateEraserPreview(); diff --git a/src/style/_app-shell.scss b/src/style/_app-shell.scss index f32be5d..abf8824 100644 --- a/src/style/_app-shell.scss +++ b/src/style/_app-shell.scss @@ -121,15 +121,12 @@ html > body { position: absolute; top: 0; left: 0; - max-width: calc(100% - var(--normal-margin) * 2); margin: var(--normal-margin); z-index: 5; pre { font-size: 20px; color: red; - white-space: pre-wrap; - overflow-wrap: anywhere; } } } diff --git a/src/style/_config-pane.scss b/src/style/_config-pane.scss index ad7ae31..ad5402f 100644 --- a/src/style/_config-pane.scss +++ b/src/style/_config-pane.scss @@ -142,7 +142,6 @@ --tp-blade-value-width: min(210px, 62%); --tp-container-unit-size: 18px; - padding-bottom: 10px; font-size: 11px; // Tweakpane v4 internal class — re-verify on upgrade. diff --git a/src/utils/graphics/initialize-gpu.test.ts b/src/utils/graphics/initialize-gpu.test.ts deleted file mode 100644 index 44bbd91..0000000 --- a/src/utils/graphics/initialize-gpu.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; - -import { initializeGpu } from './initialize-gpu'; - -const limits = { - maxBufferSize: 1024, - maxComputeWorkgroupsPerDimension: 16, - maxStorageBufferBindingSize: 1024, -} as unknown as GPUSupportedLimits; - -const createDevice = (): GPUDevice => - ({ - addEventListener: vi.fn(), - lost: new Promise(() => undefined), - }) as unknown as GPUDevice; - -const createAdapter = (features: Array = []): GPUAdapter => { - const device = createDevice(); - return { - features: new Set(features), - info: {}, - limits, - requestDevice: vi.fn().mockResolvedValue(device), - } as unknown as GPUAdapter; -}; - -const stubSecureWebGpu = (requestAdapter: GPU['requestAdapter']): void => { - vi.stubGlobal('window', { isSecureContext: true }); - vi.stubGlobal('navigator', { - gpu: { - requestAdapter, - }, - }); -}; - -describe('initializeGpu', () => { - afterEach(() => { - vi.unstubAllGlobals(); - }); - - it('starts with the least demanding compatibility adapter request', async () => { - const adapter = createAdapter(); - const requestAdapter = vi.fn().mockResolvedValue(adapter); - stubSecureWebGpu(requestAdapter as GPU['requestAdapter']); - - await initializeGpu(); - - expect(requestAdapter).toHaveBeenNthCalledWith(1, { - featureLevel: 'compatibility', - }); - expect(requestAdapter).toHaveBeenCalledTimes(1); - expect(adapter.requestDevice).toHaveBeenCalled(); - }); - - it('continues trying adapters if one request throws', async () => { - const adapter = createAdapter(); - const requestAdapter = vi - .fn() - .mockRejectedValueOnce(new Error('adapter request failed')) - .mockResolvedValueOnce(adapter); - stubSecureWebGpu(requestAdapter as GPU['requestAdapter']); - - await expect(initializeGpu()).resolves.toBeDefined(); - expect(requestAdapter).toHaveBeenCalledTimes(2); - }); - - it('falls back through core and high-performance adapter requests', async () => { - const adapter = createAdapter(); - const requestAdapter = vi - .fn() - .mockResolvedValueOnce(null) - .mockResolvedValueOnce(null) - .mockResolvedValueOnce(adapter); - stubSecureWebGpu(requestAdapter as GPU['requestAdapter']); - - await initializeGpu(); - - expect(requestAdapter).toHaveBeenNthCalledWith(1, { - featureLevel: 'compatibility', - }); - expect(requestAdapter).toHaveBeenNthCalledWith(2, undefined); - expect(requestAdapter).toHaveBeenNthCalledWith(3, { - featureLevel: 'compatibility', - powerPreference: 'high-performance', - }); - expect(adapter.requestDevice).toHaveBeenCalled(); - }); - - it('requests only the core feature when the adapter exposes optional features', async () => { - const adapter = createAdapter(['core-features-and-limits', 'timestamp-query']); - const requestAdapter = vi.fn().mockResolvedValue(adapter); - stubSecureWebGpu(requestAdapter as GPU['requestAdapter']); - - await initializeGpu(); - - expect(adapter.requestDevice).toHaveBeenCalledWith({ - requiredFeatures: ['core-features-and-limits'], - }); - }); -}); diff --git a/src/utils/graphics/initialize-gpu.ts b/src/utils/graphics/initialize-gpu.ts index 1fdc9bf..9108060 100644 --- a/src/utils/graphics/initialize-gpu.ts +++ b/src/utils/graphics/initialize-gpu.ts @@ -15,30 +15,7 @@ const REQUESTED_LIMIT_NAMES = [ 'maxComputeWorkgroupsPerDimension', ] as const satisfies ReadonlyArray; -interface AdapterRequestAttempt { - label: string; - options?: GPURequestAdapterOptions; -} - -const ADAPTER_REQUEST_ATTEMPTS: ReadonlyArray = [ - { - label: 'compatibility-default', - options: { featureLevel: 'compatibility' }, - }, - { - label: 'core-default', - }, - { - label: 'compatibility-high-performance', - options: { featureLevel: 'compatibility', powerPreference: 'high-performance' }, - }, - { - label: 'core-high-performance', - options: { powerPreference: 'high-performance' }, - }, -] as const; - -const getRelevantLimits = ( +const getRequiredLimits = ( limits: GPUSupportedLimits ): Record<(typeof REQUESTED_LIMIT_NAMES)[number], number> => Object.fromEntries(REQUESTED_LIMIT_NAMES.map((name) => [name, limits[name]])) as Record< @@ -65,64 +42,25 @@ const getAdapterInfo = (adapter: GPUAdapter): Record => { } }; -const getRequiredFeatures = (adapter: GPUAdapter): Array => { - const requiredFeatures: Array = []; - - if (adapter.features.has('core-features-and-limits')) { - requiredFeatures.push('core-features-and-limits'); - } - - return requiredFeatures; -}; - -type AdapterRequestOutcome = 'adapter' | 'unavailable' | 'error'; - -const describeAdapterRequest = ( - attempt: AdapterRequestAttempt, - outcome: AdapterRequestOutcome, - causeMessage?: string -): Record => ({ - label: attempt.label, - featureLevel: attempt.options?.featureLevel ?? 'core', - powerPreference: attempt.options?.powerPreference ?? 'default', - outcome, - ...(causeMessage === undefined ? {} : { causeMessage }), -}); - const requestAdapter = async ( - gpu: GPU -): Promise<{ - adapter: GPUAdapter | null; - attempts: Array>; -}> => { - const attempts: Array> = []; - - for (const attempt of ADAPTER_REQUEST_ATTEMPTS) { - try { - const adapter = await gpu.requestAdapter(attempt.options); - attempts.push(describeAdapterRequest(attempt, adapter ? 'adapter' : 'unavailable')); - - if (adapter) { - return { adapter, attempts }; + gpu: GPU, + options?: GPURequestAdapterOptions +): Promise => { + try { + return await gpu.requestAdapter(options); + } catch (error) { + throw new RuntimeError( + ErrorCode.WEBGPU_ADAPTER_UNAVAILABLE, + 'Could not request a WebGPU adapter.', + { + cause: error, + details: { + causeMessage: getErrorMessage(error), + powerPreference: options?.powerPreference ?? 'default', + }, } - } catch (error) { - attempts.push(describeAdapterRequest(attempt, 'error', getErrorMessage(error))); - } + ); } - - return { adapter: null, attempts }; -}; - -const formatAdapterAttemptSummary = ( - attempts: Array> -): string => { - if (attempts.length === 0) { - return 'No adapter requests were attempted.'; - } - - return `Adapter attempts: ${attempts - .map((attempt) => `${attempt.label}: ${attempt.outcome}`) - .join('; ')}`; }; export const initializeGpu = async (): Promise => { @@ -143,43 +81,35 @@ export const initializeGpu = async (): Promise => { }); } - const { adapter, attempts } = await requestAdapter(gpu); - ErrorHandler.addMetadata('webgpuAdapterRequest', { - attempts, - selectedAttempt: attempts[attempts.length - 1]?.label ?? null, - }); + const adapter = + (await requestAdapter(gpu, { + powerPreference: 'high-performance', + })) ?? (await requestAdapter(gpu)); if (!adapter) { throw new RuntimeError( ErrorCode.WEBGPU_ADAPTER_UNAVAILABLE, - [ - 'WebGPU is available, but this browser could not provide a compatible GPU adapter.', - formatAdapterAttemptSummary(attempts), - ].join('\n'), - { - details: { - attempts, - hasNavigatorGpu: true, - isSecureContext: window.isSecureContext, - platform: navigator.platform, - userAgent: navigator.userAgent, - }, - } + 'WebGPU is available, but this browser could not provide a compatible GPU adapter.' ); } - const requiredFeatures = getRequiredFeatures(adapter); + const requiredLimits = getRequiredLimits(adapter.limits); + const requiredFeatures: Array = []; + if (adapter.features.has('timestamp-query')) { + requiredFeatures.push('timestamp-query'); + } ErrorHandler.addMetadata('webgpuAdapter', { features: Array.from(adapter.features).sort(), info: getAdapterInfo(adapter), requiredFeatures, - relevantLimits: getRelevantLimits(adapter.limits), + requiredLimits, }); let gpuDevice: GPUDevice; try { gpuDevice = await adapter.requestDevice({ requiredFeatures, + requiredLimits, }); } catch (error) { throw new RuntimeError( @@ -190,6 +120,7 @@ export const initializeGpu = async (): Promise => { details: { causeMessage: getErrorMessage(error), requiredFeatures, + requiredLimits, }, } ); diff --git a/src/vibe-registry.ts b/src/vibe-registry.ts deleted file mode 100644 index 0b0ae54..0000000 --- a/src/vibe-registry.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { appConfig } from './config'; -import type { VibeId, VibePreset } from './config/types'; - -export const VIBE_PRESETS: Array = appConfig.vibes.presets; - -export const getVibeById = (vibeId: VibeId): VibePreset | undefined => - VIBE_PRESETS.find((vibe) => vibe.id === vibeId); diff --git a/src/vibe-uri.test.ts b/src/vibe-uri.test.ts index 72c04f2..fe55d5e 100644 --- a/src/vibe-uri.test.ts +++ b/src/vibe-uri.test.ts @@ -11,9 +11,9 @@ describe('vibe URI handling', () => { expect(getVibeIdFromUri('https://example.test/?vibe=Aurora%20Mycelium')).toBe( VibeId.AuroraMycelium ); - expect( - getVibeIdFromUri('https://example.test/?vibe=Velvet%20Observatory%20Copy') - ).toBe(VibeId.VelvetObservatory); + expect(getVibeIdFromUri('https://example.test/?vibe=Velvet%20Observatory%20Copy')).toBe( + VibeId.VelvetObservatory + ); }); it('uses query values before path or hash fallbacks', () => {