simvx.core.graphics_quality

GraphicsQuality: the authoritative quality-tier table.

One string dial, WorldEnvironment.quality_tier, selects a tier; each named tier resolves to a :class:QualitySettings record of dials through the single TIER_TABLE below. Both backends resolve through this table (the env-sync spec applies it inside apply_spec), so a tier means the same thing on Vulkan desktop and WebGPU web.

Tier semantics:

  • "auto" (the default): detect-default. No table lookup; each backend keeps its own detection (the web runtime resolves a tier from the WebGPU capability probe in quality_tier.js, desktop runs at its built-in defaults). The per-dial WorldEnvironment Properties still apply exactly as before, so the default path is byte-identical to the pre-tier engine.

  • "low" / "medium" / "high" / "ultra": the table drives every wired dial. A per-dial Property explicitly moved OFF its default still wins over the tier value (an escape hatch for one-dial tweaks); use "custom" to drive everything by hand. "high" is pinned to the engine defaults (QualitySettings()), so selecting it renders identically to "auto" on desktop.

  • "custom": pass-through. The table is never consulted; the per-dial Properties are the whole story (same wiring as "auto", minus any future auto-detection).

Wired dials today (each rides its existing env-sync row and transition path: resize/drain on desktop, tier-invalidation on web): render_scale, probe_blend_count + probe_face_size, shadow_caster_count. The remaining dials are declared here so later work (debug views, SSR, FFT ocean, volumetric quality) consumes the same table instead of growing their own; they change nothing until a consumer lands.

One wired dial is a SHADER PERMUTATION dial on desktop: probe_blend_count is compiled into cube_textured.frag as a #define, so moving it recompiles the lit fragment permutations, drains the device and rebuilds the mesh pipelines. shadow_caster_count reallocates both shadow atlases, which drains the device too but compiles nothing. Either is a one-off cost at the transition and nothing per frame, but it is why a tier is something a scene selects, not something it animates.

Module Contents

Classes

QualitySettings

One tier’s dial record. Field defaults ARE the engine defaults (the "high" tier), so QualitySettings() always names today’s look.

Functions

resolve_tier

The :class:QualitySettings for a named tier, else None.

tier_dial_overrides

Env-attr -> effective value for the wired dials of env’s tier.

Data

API

simvx.core.graphics_quality.__all__

[‘QUALITY_TIERS’, ‘QualitySettings’, ‘TIER_TABLE’, ‘resolve_tier’, ‘tier_dial_overrides’]

simvx.core.graphics_quality.QUALITY_TIERS: tuple[str, ...]

(‘low’, ‘medium’, ‘high’, ‘ultra’)

class simvx.core.graphics_quality.QualitySettings[source]

One tier’s dial record. Field defaults ARE the engine defaults (the "high" tier), so QualitySettings() always names today’s look.

ssr_mode / ssao_mode / volumetric_quality are CEILINGS on the matching user-enabled feature (like WorldEnvironment.ambient_mode): a tier never switches a feature on, it caps how expensively it runs.

render_scale: float

1.0

shadow_map_size: int

2048

probe_blend_count: int

2

probe_face_size: int

128

shadow_caster_count: int

1

ssr_mode: str

‘half’

ssao_mode: str

‘on’

ssgi_mode: str

‘off’

ocean_size: int

256

ocean_cascades: int

3

volumetric_quality: str

‘high’

taau: bool

True

simvx.core.graphics_quality.TIER_TABLE: dict[str, simvx.core.graphics_quality.QualitySettings]

None

simvx.core.graphics_quality.resolve_tier(tier: str) simvx.core.graphics_quality.QualitySettings | None[source]

The :class:QualitySettings for a named tier, else None.

"auto" and "custom" (and anything unknown: the Property enum on WorldEnvironment.quality_tier already rejects those) resolve to None = “no table, per-dial Properties / backend detection decide”.

simvx.core.graphics_quality.tier_dial_overrides(env: Any) dict[str, Any][source]

Env-attr -> effective value for the wired dials of env’s tier.

Empty for "auto" / "custom" (pass-through: the env-sync spec reads the per-dial Properties directly, exactly the pre-tier behaviour, so the default path costs one dict lookup). For a named tier, every wired dial still at its Property default maps to the tier’s value; explicitly-set dials are omitted so the Property wins. apply_spec consults this on both backends: the ONE resolution point for quality tiers.