Skip to content

Latest commit

 

History

History
499 lines (433 loc) · 32.7 KB

README.md

File metadata and controls

499 lines (433 loc) · 32.7 KB

Configuration ownership

Amadeus uses environment variables as startup input, not as a general runtime state store.

Precedence and entry points

  1. The parent process environment has highest priority. Electron, CI, or a launcher can use it to define one locked process profile.
  2. Electron desktop settings supply user-managed connection values to the Python child process. Secrets are encrypted through Electron safeStorage and are never returned to the renderer.
  3. The repository-root .env supplies local developer values only when the parent/desktop launch profile did not already define a key.
  4. config/settings.py parses application startup values and remains the compatibility import surface for Python callers.
  5. Function arguments or explicitly owned runtime objects carry values that can change after startup. They must not be written back into os.environ.

.env.example is the public, curated setup template. .env is local and must not be committed. Defaults that ordinary users should not tune stay in config/settings.py instead of making the template unreadable.

config/environment.py is the shared dotenv and type-parsing boundary. It loads one reader per project root, preserves the existing process-over-dotenv precedence, and records every setting declared through it. New startup configuration should use this boundary rather than calling load_dotenv again.

Development bootstrap is an explicit exception: AMADEUS_DEV_PORT is read by electron/src/main/developmentServer.ts before Python exists. It reads only this key from the repository-root .env, with process environment taking priority and 5173 as the default. Supply a literal integer from 1 to 65535 (no variable interpolation). Both development commands and Electron use that resolver; Electron passes the resolved port to its Python child. Headless Python uses config/development.py with the existing EnvironmentReader to accept the same exact development origin for HTTP/WebSocket requests. This is a development transport setting, not a desktop Settings field. Changing it requires restarting the development launcher, not just the backend. Production frontend builds do not read this setting, and packaged Electron never trusts a development URL. Invalid input stops the development launcher. Python instead disables the development browser origin and logs a warning, so this development-only setting cannot prevent native/file-origin backend clients from starting.

Shared configuration declarations

The shared catalog covers the TTS selector, all four built-in TTS connection/model groups, graphics, voice input/reference controls, model connections, model roles, Provider connections, character, desktop and live presentation/vision settings. Edit the owning JSON for defaults, types, options, ranges, desktop editability, application policy, and English/Chinese labels. The configuration keys retain their existing environment-variable names. Run npm run generate:config from electron after editing a declaration.

Python reads the JSON through config/catalog/__init__.py and the existing EnvironmentReader; settings.FISH_TTS_* callers keep working. The generator maintains explicit, typed bindings in a marked block of settings.py, so Ruff and editors can resolve the same public names without globals() injection. Electron consumes the generated src/shared/configCatalog.generated.ts, which is compiled into both main and renderer bundles. Offline Settings never requires a running Python process or access to the source checkout. The generator also maintains marked sections of .env.example. Empty strings are quoted so option comments cannot become values; secret examples stay commented out. Uncomment and fill only the credentials you use. Commit generated output with its declaration; npm test and npm run build reject stale output. Removed or renamed groups lose their old managed env sections; duplicate declared keys outside those sections are rejected. catalog_legacy.json bounds the remaining handwritten declarations: new keys and migrated keys cannot be added to legacy desktop lists/forms or Python parsing/status definitions. The desktop list now contains only structured ACP records and removal of the retired route key. Internal Python tuning fields outside Settings remain in the bounded legacy inventory.

Desktop snapshots distinguish stored overrides from known non-secret startup inputs. The form follows source precedence even when offline. Dotenv interpolation remains Python-owned: an unresolved offline value is shown as unknown, and a pending clear never reuses the running backend's superseded value. Secrets expose only configured state. Computed defaults for the next launch use the projected inputs, so changing a local model also changes an unset Hybrid model without being overwritten by the old running value.

desktop_default: { "value": true, "platforms": ["win32"] } declares a desktop-only launch default; omit platforms to apply it on every desktop OS. The launch environment and offline snapshot derive it from the same declaration, after process, saved-user and dotenv overrides. Python's headless default stays unchanged. Defaults equal to Python's must not be injected unnecessarily: AEC delay key presence denotes an explicit override of device-class calibration. Catalog JSON resources are packaged recursively; adding a new folder does not require new package entries in pyproject.toml.

apply is frontend, host, backend_restart or desktop_restart, with a field override when its group has mixed behavior. Desktop-only fields never enter the backend environment. A one-to-one Host setting declares its runtime_key; forms, persistence mapping and basic runtime validation derive from that declaration. The implementation still owns applying the value. Save failures never apply a change, and live application failures leave the saved revision pending. Only the matching application acknowledgment clears it.

Supported field types are string, path, URL, enum, boolean, integer and number. Move existing labels, explanations, icons and translations intact when migrating a field. Option descriptions belong in their localized description; a renderer that displays them must retain that content. An option marked hidden remains an accepted configuration value but is not newly offered by the form. This preserves the existing Vision and AUIP choice sets without rejecting saved or environment inputs. Field icon preserves an existing control's icon instead of replacing it with the group's default. Voice groups may use their existing group order for UI placement independently of backend registration order. The local-engine owner keeps the CLI executable label specific to the selected engine. Where existing UI guidance differs from backend diagnostic copy, the group retains both through ui_description and description, instead of replacing one audience's explanation with the other's. Fields use the same override; ui_description: null preserves an intentionally absent form explanation while retaining an existing backend diagnostic. computed_default: true omits the static default: its owner supplies a value when resolving it. Texture sampling still defaults to whether the effective frame rate is 60 FPS, with explicit choices taking precedence. Graphics presets, TTS device selection and atomic checkpoint-pair resolution remain ordinary code. The wallpaper caption default belongs to the presentation owner because the legacy combined subtitle input may select source, bilingual or disabled captions. An unresolved offline computed value stays unknown and editable. An example may differ from the runtime default (for example the curated Kurisu setup); example_active preserves whether that env example is enabled. Boolean true_values is used only where an existing owner accepts additional spellings such as on; other booleans retain the shared parser's contract.

Each TTS declaration has voice_backend metadata and a section (output or remote). These drive the existing TTS registry, offline selector, Settings placement and backend status projection. To add a built-in TTS backend, add its declaration plus implementation: factory names its constructor, probe reports readiness, and streaming is either a boolean or an implementation function. Entry points are resolved lazily; listing backends never constructs a model. The implementation owns protocol validation and optional dependency checks. Add backend-specific tests, then regenerate; no central backend switch or Settings allowlist needs editing. New env sections are appended automatically.

This is an internal catalog of packaged application data, not an extension manifest loader. Third-party code still needs the extension host's lifecycle and authorization boundary. ASR fields, shared reference/emotion controls and live runtime application retain their existing owners. TTS mode/language remain bounded composite conversions; window enumeration, character management and theme previews keep their dedicated UI behavior. ACP/MCP records keep their existing structured validation, encrypted storage and authorization boundaries.

The catalog describes fields; it does not store user values or prove that a backend is available. DesktopSettingsStore still owns encryption, process locks, clearing, durable saves and pending revisions. Runtime probes still own availability; an incomplete unselected provider must not prevent startup. Secret declarations have no default, and status responses expose only whether a credential is configured. Protocol-specific validation stays in the backend.

Handler composition

server/handlers/voice.py owns construction and binding of TTS, ASR and Wake handlers. The built-in composition table registers the domain's handlers before starting the server; app.py supplies runtime dependencies later through configure(). Add a handler using existing voice dependencies inside this domain; app.py does not need a new import, constructor, registration entry or configure call. New cross-domain dependencies still belong at the application composition root.

The domain closes the current Wake and ASR instances before the shared microphone; shutdown never invokes lazy factories. Shared service creation and replacement remain with the existing scene/runtime owners. WebSocket registration rejects duplicate method ownership before inserting any methods from the new handler.

Chat image input

DeepSeek image input is available with DEEPSEEK_MODEL_NAME=deepseek-flash. The documented deepseek-v4-flash and deepseek-v4-flash-vision-exp aliases also accept images. DeepSeek Pro and legacy text models remain text-only. See the DeepSeek vision guide.

Both direct DeepSeek Chat and the DeepSeek tail of hybrid2 send the image using the existing OpenAI-compatible image_url format. The hybrid local head receives only a text acknowledgement hint. Images are attached to the current user turn, not stored in conversation history. The desktop image button follows the backend's selected-model capability; changing the model in startup settings requires restarting the backend.

Graphics profiles

GRAPHICS_PROFILE is the startup owner for the shared PixiJS render budget:

  • standard is the default and uses 60 FPS with the native device-pixel ratio.
  • power_saving uses 30 FPS and caps resolution at 1.5.
  • custom uses RENDER_MAX_FPS (10–240) and RENDER_MAX_RESOLUTION (0.25–4.0).

The parsed effective values are propagated to chat, Electron, Lively, and Wallpaper Engine render surfaces. A missing effective resolution limit means native DPR; it must not be serialized as zero, NaN, or the string None. Wallpaper Engine's valid general fps property is a runtime host constraint, so the renderer uses the lower of it and the project profile. Unsupported host values restore the project limit. This runtime constraint does not mutate the startup environment.

RENDER_TEXTURE_SAMPLING defaults on when the effective startup limit is 60 FPS, and off at other rates. An explicit true/false choice overrides the default. The 30 FPS fast-transition timing tradeoff remains opt-in. Changing sampling requires a backend restart and reopening render surfaces.

Migration from an older .env.example: an existing RENDER_TEXTURE_SAMPLING=false remains an explicit override, even if it was copied from that template. Remove/comment that line to use the new startup default, or set it to true to enable sampling explicitly. Updates do not rewrite the user's .env or guess whether an old value was intentional.

Once sampling is enabled, a Wallpaper Engine host-cap change rebuilds the sampling plans at the new effective FPS on their next use. It preserves authored playback time, the logical position and exact held/required frames. For example, a 120-frame, 5 ms transition keeps its 600 ms duration when the host changes from 60 to 30 FPS; the unsampled legacy 30 FPS path can take 1 s. The host cap does not toggle the user's sampling choice. Sampling-disabled sessions stay disabled, and a 30 FPS startup default is still off.

RENDER_BC7_CACHE defaults true for the shared HTTP wallpaper asset server. On GPUs exposing WebGL BPTC, displayed UASTC frames are saved as derived BC7 in the background, then reused on later loads and application launches. The texture-store budget is unchanged by the cache (see Animation frame source). Derived files target 4 GiB under %LOCALAPPDATA%/Amadeus/texture-cache/bc7-v1 on Windows, ~/Library/Caches/Amadeus/texture-cache/bc7-v1 on macOS, and $XDG_CACHE_HOME/Amadeus/texture-cache/bc7-v1 (or ~/.cache/...) on Linux. Only owned flat cache files are pruned; character packs are never modified. Concurrent app instances reconcile the shared directory on writes every 30 seconds, so the disk target may be temporarily exceeded between scans. New writes also preserve a 1 GiB free-space reserve, checked before allocating the temporary file (including its size) and before publication. This is a best-effort guard against concurrent external disk use, not a volume reservation. Low space pauses derivation; existing cache reads and playback continue.

Cache identity includes source content and the bundled transcoder revision. Writes publish atomically. Corrupt entries fall back to the UASTC source and are rebuilt as their frames are displayed. Unsupported GPUs/layouts, file:// legacy rendering, and unavailable cache storage retain the source path. Startup derivation is bounded to one compression worker and two pending frames, using zstd level 3 to limit interactive CPU work. It does not prebuild an entire pack. Cache disk space, worker/transient buffers, and reclaimable OS file cache are separate from the texture-store residency budget.

Identical publications succeed without replacing an in-use file. Failed evictions remain indexed and charged. Temporary write failures (including 503 sharing/space errors) cool down for 30 seconds, then a later playback load can try again; no payloads or autonomous retries are queued during cooldown. 403 disables writes for that page's capability lifetime, without disabling reads. A stalled/crashed worker is retired with only its own pending jobs; later requests create a replacement. Three consecutive worker incidents in one channel pause that channel for 30 seconds. Decode and encode health are independent, and a worker interruption does not mark valid disk data corrupt. Temporary cache-directory initialization errors likewise retry on a later asset request after a 30-second cooldown, without changing the configured flag.

What belongs where

  • Credentials, model paths, ports, startup feature flags: .env -> config/settings.py -> imported constant or injected constructor argument.
  • Electron launch-profile defaults: the field's desktop_default in the catalog. These may be intentionally different from headless Python defaults. The current desktop profile enables realtime AEC/barge-in while headless Python does not.
  • Runtime choices changed by UI or request handling: a named runtime owner. tts/pre_translation_runtime.py is one example.
  • Test isolation and subprocess metadata: the test/launcher that creates the child process. These are not application settings.
  • Script-local variables such as executable paths or encoding flags: the script, unless Python application code also consumes the same value.

Electron settings contract

system.get_config returns current values from their real owners and system.set_config accepts an explicit runtime allowlist only. Unsupported keys fail instead of being reported as updated. Model and Work Provider connection cards use a separate Electron-owned desktop profile. Non-secret values are persisted in the app user-data directory; secrets are stored only as operating-system-encrypted ciphertext. Saving a startup value never mutates the repository .env and requires a backend restart. An explicit parent environment value remains authoritative and is shown as locked in the GUI.

Skills and MCP connections are Host-installed Work Provider capabilities. They are not a Main Chat tool surface: a Provider manifest must explicitly accept the capability projection before the Settings catalog shows it as a consumer.

TTS mode and language changes are accepted only while Chat and playback are idle. TTS_BACKEND defaults to the embedded Amadeus GPT-SoVITS v3 rewrite, which accepts v1, v2, v2Pro, v2ProPlus and v3 GPT/SoVITS checkpoint pairs. Selecting openai_compatible avoids importing the local model stack and sends synthesis text to the explicitly configured speech endpoint. Remote TTS defaults to broadly compatible buffered WAV responses; TTS_API_STREAM_PROTOCOL=openai_sse explicitly enables PCM first-packet playback for endpoints that implement OpenAI speech SSE events. There is no automatic retry from a partial stream to a second billable request. TTS_BACKEND=fish_audio selects Fish Audio's MessagePack WebSocket transport. Set FISH_TTS_API_KEY, FISH_TTS_MODEL (for example s2.1-pro-free), and FISH_TTS_REFERENCE_ID (the hosted voice ID, not the inference model). The default reference is the public Japanese Makise Kurisu voice b450b19370434173b121446057622e9b. FISH_TTS_LATENCY=balanced is the default; FISH_TTS_WS_URL defaults to wss://api.fish.audio/v1/tts/live. Install the voice extra for MessagePack support. Chat/VN keep their existing sentence scheduling; audio streams into the existing playback pipeline. The adapter also accepts asynchronously arriving text chunks for duplex experiments. See Fish Audio setup and probe. ASR_BACKEND selects only the full Conversation recognizer and defaults to Qwen3-ASR, preserving context prompting and speculative endpoint optimization. WAKE_ASR_BACKEND is an independent always-on role and may keep SenseVoice loaded alongside Qwen. A remote Conversation ASR intentionally disables partial speculative API calls to avoid hidden duplicate network requests and metered usage.

The Electron Voice settings use the same precedence and encrypted-secret store as model connections. ASR_API_KEY, TTS_API_KEY, and FISH_TTS_API_KEY are never returned to the renderer. Remote voice backends are selected explicitly; local failures never silently upload microphone audio or synthesis text. The first-release Main Chat default is remote DeepSeek; local model settings apply only when the user explicitly selects the pure-local profile. LOCAL_LLM_TYPE is editable for the pure local provider and synchronizes ChatRuntime with the synchronous fallback. LLM_PROVIDER is the only chat router; the deprecated USE_LOCAL_LLM value no longer overrides it. LOCAL_LLM_LAUNCH_MODE=external|managed controls only ownership of the default llama.cpp server process. LM Studio, Ollama, and llama-cli remain pure-local compatibility profiles with type-specific fields. The hybrid* providers use the dedicated OpenAI-compatible HYBRID_LOCAL_LLM_URL and HYBRID_LOCAL_LLM_MODEL; they never branch on LOCAL_LLM_TYPE.

Direct os.environ reads are still valid at genuine process boundaries (for example an isolated legacy provider helper), but they should not duplicate an already parsed startup setting. A direct write is reserved for child-process construction or a documented third-party compatibility contract.

Intentional late reads

These are not pending mechanical migrations:

Owner Values Why they remain late-bound
tts/aec_realtime.py explicit AEC delay Only presence is checked; the value comes from parsed settings. An explicit value disables device-class calibration.
tts/pipeline.py ENABLE_CUDA_GRAPH The compatibility mode function and bundled inference code read the value at synthesis time. There is no active mainline UI caller today, so no replacement runtime contract is invented yet.
provider/session storage helpers AMADEUS_*_PATH values Helpers accept explicit path injection and subprocess tests supply isolated stores at their process boundary.
wallpaper scenario helpers wallpaper/scenario overrides Both wallpaper hosts resolve component-local media overrides without importing the heavyweight application settings facade.
vn_player/runtime.py VN_* values These describe one VN session, not the application startup snapshot.
server/runtime_status.py build/workspace metadata The launcher supplies diagnostic facts for the current process.
asr/qwen3_asr_sidecar.py QWEN3_ASR_DEVICE, QWEN3_ASR_REQUIRE_CUDA The isolated interpreter reads the environment supplied by its parent.
tts/gpt_sovits_sidecar.py TTS_REQUIRE_CUDA The isolated interpreter enforces the device requirement supplied by its parent.
server/presentation_runtime.py AMADEUS_PRESENTATION_LOCALE, AMADEUS_WALLPAPER_CAPTION_MODE Presentation resolves its current, component-owned preferences.
server/handlers/system_handler.py Browser/VN role provider and model overrides Existing status projections distinguish explicit overrides from inherited values. Their empty-value semantics are retained; the ratchet does not mechanically replace these reads.
server/app.py AMADEUS_ALLOW_MAIN_VOICE_DURING_VN The existing voice-route callback checks this override while deciding whether main voice may run during a VN session.
server/vn_tts_bridge.py, tts/utterance_scheduler.py dispatch, queue and utterance limits Existing bridge/scheduler owners resolve their timing and grouping controls. Changing when they are read requires a separate behavior review.
server/wallpaper_subtitle_translator.py translation timeout and token budget Each translator resolves its own request limits at construction.

The inventory also retains startup reads in server/visual_runtime.py and server/chat_translation_runtime.py. These initialize component-owned mutable state using catalog defaults, then accept live changes through set_config. They are legacy parsing sites, not justification for introducing new raw reads. The helper-aware audit added 47 existing file/key pairs across eight files; eight of these refer to declared catalog keys. No runtime reads were moved.

catalog_legacy.json.environment_reads records exact file/key pairs, including process-boundary and remaining legacy reads. The AST scan recognizes literal keys in direct reads and same-file parameter-forwarding helpers, including nested helpers and positional/keyword calls. It does not resolve arbitrary runtime expressions, callbacks, or helpers imported from other modules; this is a bounded source guardrail, not a complete runtime inventory. New product settings belong in the catalog. A new intentional late read needs an explicit baseline review and an explanation here; moving a read to another file is not automatically permitted. python tools/maintainability_ratchet.py --update only tightens existing baselines and refuses new entries. It also reports silent broad exceptions for inspection; that observation is not a CI gate.

Serialized state writes

config/durable_io.py provides write_text and write_bytes without loading application settings. Callers own serialization, parent-directory creation, locking, validation, exclusive creation and recovery. The file is written and synced in the destination directory before replacement. On Windows, known sharing/access conflicts receive bounded retries on ordinary threads. A call on a running asyncio event-loop thread propagates the first conflict without sleeping, preserving synchronous caller ordering and avoiding retry backoff that would stall unrelated events. Other errors propagate immediately. The write and file sync themselves remain synchronous.

Replacement is the commit point. Pre-commit I/O errors preserve the previous file. A later directory-sync error logs a warning and returns normally: the new contents are visible but crash durability is unconfirmed. This does not promise multi-file transactions or coordinate concurrent writers. Text uses UTF-8 with no platform newline translation; existing Windows JSON saves may consequently change CRLF to LF without changing their parsed content or trailing-newline convention.

The VN context store is explicitly deferred (2026-10-11). Its non-atomic fallback and recovery behavior are unchanged. Future work must distinguish a new session from missing/corrupt state in an existing session, preserve corrupt evidence, recover sequence ordering or refuse continuation on every subsequent startup, and define policies for all JSON files that are read and later overwritten. Permission failures must not be treated as malformed content. The R3 exception is limited to VNContextStore.write_json, not a general exception for VN code.

Built-in and extension configuration vocabularies

The catalog is trusted, packaged application data. An external extension manifest is a separate validation and lifecycle boundary; this documentation does not enable extension discovery or runtime registration.

Vocabulary Built-in catalog Extension design boundary
type String, path, URL, enum, boolean, integer, number No path type in the current draft; do not silently grant filesystem access.
title, description English and Chinese localized maps May be a string or language map; normalize representation and enforce manifest length limits.
default Must match the declared field type Must match the type and constraints; a secret cannot declare a default, including an empty one.
secret Host-owned string credentials Host-owned bindings; omit built-in empty defaults when constructing a manifest, rather than copying them.
options Strings or objects with required label and optional description/hidden Draft options permit optional labels and have a bounded count; adapt explicitly.
min, max, max_length Validated field constraints Common concepts, with manifest-specific bounds and applicability.
apply frontend, host, backend_restart, desktop_restart live or reactivate; these describe extension generations and are not interchangeable with built-in restart policy.
required Not a built-in field attribute Missing required configuration keeps the extension in needs_config.
extension_selection Identifies the supported contribution kind/role for an existing selector; currently TTS and interface theme only Not a manifest field; the Host support profile owns selection and application policy.
Runtime and presentation wiring runtime_key, scope, setting, computed/desktop defaults, visibility, factories and probes Host implementation details, not a public manifest surface.

The generator already rejects unknown built-in attributes. Share only genuinely common value checks; keep manifest authority, secrets, application policy and lifecycle validation with their owners. A real out-of-tree adapter must exercise installation, configuration, enable/disable, failure and removal before fixing the registration API, followed by a second adapter to validate its generality.

Compatibility notes

  • TTS_DEVICE=auto resolves to mps on Apple Silicon, cpu on Intel macOS, and the existing cuda:0 default elsewhere. The resolved device is copied to os.environ because the bundled BigVGAN loader directly consumes that variable. An explicit indexed CUDA device or explicit mps/cpu value is preserved.
  • Local GPT-SoVITS CPU synthesis automatically uses the existing persistent TTS sidecar (the current Python interpreter unless TTS_PYTHON is set). This isolates its PyTorch thread settings from ASR/VAD dependencies. CUDA and MPS keep their embedded default; explicit sidecar/interpreter settings still select a subprocess. The sidecar serializes synthesis and drains interrupted requests before accepting the next one; CPU compute is still shared with the host. All synthesis modes consume blocking model/IPC streams on the TTS executor, keeping the event loop available to ASR.
  • AMADUES_PRE_TRANSLATION_ENABLED remains accepted as a deprecated spelling of AMADEUS_PRE_TRANSLATION_ENABLED at the pre-translation boundary.
  • The legacy root GPT-SoVITS WebUI/API entry points and their conflicting config.py were removed. A future HTTP API should be designed around the current server.app contracts instead of reviving that compatibility layer.

Model connection inheritance stays in the model owner. The catalog records legacy names (aliases), facade attribute bindings (setting), and local engine field visibility (visible_when). CLI numeric controls remain parsed strings; control: "number" supplies UI hints without changing command argument types. Bedrock bearer credentials now use the same surrounding-quote/whitespace normalization as other API secrets. AWS credential discovery remains unchanged.

Role/session fields use scope: "session": their owner calls read_catalog_value against its current environment, preserving per-session overrides without loading all application settings. scope: "virtual" marks composite controls such as Codex transport; the desktop owner translates these into the real launch keys. They are not emitted as synthetic environment variables or Python settings. visible_when permits only equality against declared selectors in the same group; it contains no executable expressions. Provider availability and authorization remain runtime facts. String selectors offer choices without turning a previously open provider identifier into a new hard-coded enum. Structured ACP/MCP profiles retain their existing dedicated storage, validation and authorization owners.

Built-in handler composition

server/handlers/composition.py is the built-in factory table. It constructs all request handlers against explicitly supplied startup services and registers every result before the server accepts clients. Adding a handler changes its implementation and this table, without editing server/app.py. Runtime owners still bind their live services and callbacks; this table is not a plugin loader and grants no new extension permissions. Duplicate request methods fail startup instead of replacing the existing owner.

Run python tools/smoke_builtin_handler_catalog.py to start an isolated backend with models and external agents disabled. The smoke adds a synthetic factory, verifies an authenticated WebSocket round trip, reads the real configuration, checks voice routes, and shuts the process down cleanly.

Animation frame source

Graphics settings retain the existing profiles, FPS, density, sampling and BC7 cache controls. RENDER_FRAME_SOURCE=auto selects the optional matched video/ companion within the existing SpriteForge pack only after renderer capability checks; static selects the established static path. RENDER_FRAME_BUDGET_MIB=auto uses 384 MiB for video and 2048 MiB for static/fallback. Explicit 384/768/2048 values apply to both paths. The GUI shows observed per-surface state separately from saved choices. Restart the backend and reopen existing render windows after saving. No launcher override or old-pack path migration is required. See frame-source integration.

Qwen recognition acceleration

QWEN3_ASR_CUDA_GRAPH and QWEN3_ASR_FLASH_ATTN default to true and are independently editable in Conversation recognition settings. Both apply after a backend restart. Set the first to false for official decoding, and the second to false for PyTorch SDPA on NVIDIA CUDA. Set both to false to compare against the official SDPA path; TTS acceleration settings are independent. CPU and ROCm keep their existing attention paths. FlashAttention 2 requires a supported installed package and an NVIDIA GPU with compute capability 8.0 or newer; older NVIDIA devices use SDPA regardless of the preference.

The explicit Python inference_engine constructor argument remains an offline benchmark control; ordinary application callers use the parsed startup setting.