Accessibility, localization, and quality are release inputs for every supported surface/tier. Equivalent member jobs require explicit modality fallbacks and current evidence rather than a single global conformance claim.
Three commitments decide whether the Lilith metaverse is something everyone can
actually use, in their own language, with proof that it works: an avatar in a 3D
room has to be legible to a screen-reader user and a photosensitive user and a
one-thumbed mobile user; a class spoken in English has to read and sound right
to an attendee in Seoul or Riyadh standing in the same room; and every one of
those claims has to be measured against a hard threshold before it ships. V3
treats all three as engineering surfaces with code behind them, not aspirations
in a deck. The accessibility modes and the localization pipeline live as
decision engines inside the largest library in V3 —
@oshun/tenant-lilith-commons (libs/v3/lilith-commons, a 13,458-line
src/index.ts with a 4,536-line, 143-case test suite). The telemetry schema,
the eight per-surface eval gates, and the WCAG conformance gate live one layer
out in @oshun/analytics, deliberately adjacent to V1's evaluation pipeline so
release criteria stay versioned with the rest of Oshun. The shared string
catalog and locale formatting are V1's @oshun/i18n, reused rather than forked.
This page covers what is real in each, what is honestly a seam, and the gate
machinery that refuses to lie about either. The section hub is
../V3_features.md.
What ships, honestly#
The line between real and referenced is consistent across all three areas:
- Real and tested — the gates and validators. Every accessibility mode and
every localization surface is a
create*Plan()/build*Report()pair: a deterministic engine that takes a plan, checks it against named domain thresholds (flash rate ≤ 3 Hz, contrast ≥ 4.5, caption p95 ≤ 1 000 ms), and returns a schema-stamped report carrying apassedboolean, aqaReviewStatusofpassed/blocked, and an enumeratedfailures[]list. These are fail-loud decision engines, not config tables —buildV3…Reportreturnsblockedand names the breach the instant a budget regresses. The 143-case suite asserts both the pass path and the block path for each mode. - Real evidence, fail-closed gates. The eval-set manifest, the telemetry
schema, the eight Grafana boards, and the WCAG QA manifest are real typed
artifacts in
@oshun/analyticswith committed observed runs and validation evidence; the gate functions reject publication on any missing surface, absent threshold, or breached metric. - Referenced by string — the in-world rendering. The caption widget floating
above an avatar's head, the deuteranopia palette swap on an asana cue, the
RTL-mirrored UMG HUD — the actual Unreal rendering is content addressed by
path.
lilith-commonsowns the data, the policy, and the gate; the UEV3Telemetrymodule is an honest seam that reports its owned surface and instruments nothing (see ../architecture/observability-performance-security-and-launch.md).
Accessibility in 3D#
The seven machine-gated modes#
lilith-commons declares ten accessibility/localization capabilities in its
package descriptor, each carrying an operationalMetric string that is the gate
in shorthand. Seven are accessibility modes, each with a builder and a report
schema:
- Live avatar captions (
buildV3LiveAvatarCaptionReport, schemav3.accessibility.live-avatar-captions.v1). Speech-to-text captions are required on all three surfaces —web,mobile,vr— anchored above the speaker's avatar. The report computes per-surface p95 latency fromrenderedAtMsBySurface − spokenAtMs, enforces theV3_LIVE_AVATAR_CAPTION_LATENCY_BUDGET_MS = 1_000ms budget, and verifies the anchor genuinely floats above the head (worldPositionMeters.y > 1.2,followsAvatarTransform, screen position in the top 40%). The test asserts a block with the exact stringcaption surface mobile p95 latency 1240ms exceeds 1000mswhen a surface regresses. - Reduced motion (
createV3ReducedMotionPlan/buildV3ReducedMotionReport). Caps camera yaw atV3_REDUCED_MOTION_CAMERA_YAW_CAP_DEGREES = 6°, avatar motion at…AVATAR_MOTION_CAP_CENTIMETERS = 1cm, forcesteleportlocomotion, disables continuous parallax, and requires snap-turn-only. The "off" path deliberately emits an uncapped plan (yaw 12°, smooth-slide, parallax on) so the gate can prove it blocks the unsafe configuration. - Color-vision palettes (
buildV3ColorVisionPaletteReport). Three palettes —deuteranopia,protanopia,tritanopia— gated toV3_COLOR_VISION_REQUIRED_PALETTE_COUNT = 3, with asana-cue, marker, and label contrast each held toV3_COLOR_VISION_ASANA_CUE_MIN_CONTRAST_RATIO = 4.5(the WCAG 2.2 AA contrast floor), plus a ≥ 44 px hit area and ≥ 4 px cue line width per surface. A cue never depends on a single hue. - Photosensitive safe mode (
buildV3PhotosensitiveSafeModeReport). Holds observed flash rate toV3_PHOTOSENSITIVE_SAFE_MAX_FLASH_RATE_HZ = 3(the WCAG 2.2 SC 2.3.1 three-flash threshold), caps luminance delta, requires strobe attenuation ≥ 90%, bounds bloom intensity and emissive-pulse amplitude, locks auto-exposure, and disables rapid-cut transitions — the same constraint the concert Sequencer enforces on stage lighting. - One-handed mobile HUD (
buildV3OneHandedMobileHudReport). Docks nine primary targets into the bottom thumb zone, requiring each to start at ≥ 56% viewport height with a ≥ 44 px hit area; the inventory includes thespatial-audio-off,audio-descriptions,photosensitive-safe, andcaption-surfacetoggles, so the other accessibility modes remain reachable one-handed. - Cognitive-load reduction (
buildV3CognitiveLoadReductionReport). Caps visible primary controls at 5, holds dwell time ≥ 1 200 ms and transition dwell ≥ 1 000 ms, limits instruction copy to ≤ 7 words, and requires icon+label pairs, progressive disclosure, and disabled autoplay prompts. - Keyboard / single-switch navigation
(
buildV3KeyboardSingleSwitchNavigationReport). Full desktop operability with no pointer, scan cycle ≤ 1 200 ms — the gate that makes the 2D surfaces usable by switch-access users.
Each builder produces a report whose failures[] is computed from real
per-surface predicates, not a hardcoded verdict; flipping any input below its
floor changes the verdict and the enumerated reason. This is why the suite can
assert both report.passed === true on the canonical plan and a specific
failure string on a deliberately broken one.
Audio descriptions, spatial-audio-off, and VR comfort — where the line is#
The features brief lists more affordances than lilith-commons gates directly,
and the docs are explicit about which is which. Audio descriptions and
spatial-audio-off appear in the HUD reach inventory and the keyboard-nav
target set (audio-description-toggle, mobile-hud:spatial-audio-off) — so
they are gated for reachability — but the audio behavior itself lives in
libs/v3/spatial-audio, whose renderer is an hrtf-convolution path with an
ambisonic-stereo-fallback (the flat-stereo mix for users with HRTF-induced
disorientation). Spatial-audio-off additionally carries its own verifier
(verify:v3 spatial-audio-off-mode) in the QA manifest. VR comfort and
calibration — vignetting, snap-degree presets, IPD, floor recalibration — are
specified in the features and architecture docs and gated through the WCAG QA
manifest rather than a lilith-commons builder. Calling this out is the honest
posture: the seven modes above have deterministic report engines; the rest are
real but live in other libraries or as QA-manifest checklist items.
The WCAG 2.2 AA bar#
The target is WCAG 2.2 AA for 2D surfaces, with the W3C XR accessibility user-needs draft as a north star for the evolving XR work. Crucially, accessibility parity is a tiered-stack requirement: the Tier-2 web fallback must clear the same AA bar as the canonical UE client, and the fallback's WCAG conformance is itself a launch-readiness gate — gated, not aspirational.
Localization#
Eighteen launch locales, real HUD strings, real RTL#
V3 enumerates 18 GA launch locales with a hard floor of 16
(V3_LAUNCH_LOCALIZATION_MIN_LOCALE_COUNT = 16,
V3_LAUNCH_LOCALIZATION_REQUIRED_LOCALE_IDS): the three English variants
(en-US/GB/IN), es-ES/MX, pt-BR, fr-FR/CA, de-DE, it-IT, hi-IN,
ja-JP, ko-KR, zh-CN/TW, ar, he, and yo-NG. Yoruba is in the GA set
ahead of the African-region wave-2 launch it supports. Each locale carries a
createV3LaunchLocalizationLocalePack with its direction; ar and he are
rtl.
The six required HUD strings (hud.enter, hud.stream-status,
hud.caption-anchor, hud.accessibility-controls, hud.tara-instructor-voice,
hud.saraswati-persona-voice) are translated for all 18 locales in
launch-locale-hud-translations.ts, typed
satisfies Record<V3LaunchLocaleId, Record<V3LaunchLocalizationHudStringId, string>>
so the build fails if any locale is missing a string. The translations are
genuinely non-English — Yoruba renders "Enter Lilith" as Wọ Lilith and
"Captions" as Ọ̀rọ̀ ìsàlẹ̀, Hebrew as כתוביות — and
buildV3LaunchLocalizationLocaleReport computes a differsFromEnglish
predicate that blocks a locale whose strings were merely copied through from
en-US (English regional variants legitimately share base wording).
buildV3LaunchLocalizationReport then ANDs, across every enumerated locale,
that HUD strings are localized, the UE-UMG and Tier-2-HTML HUD bundles are
QA-validated, subtitle anchors validate on web/mobile/vr, voice dubs are
approved, and — for ar/he specifically — the RTL layout validates. A single
unvalidated bundle returns qaReviewStatus: 'blocked' with a named failure.
Voice dubbing and cultural adaptation#
Two further localization engines round out the pipeline. The voice-dubbing
pipeline (buildV3VoiceDubbingPipelineReport) requires GA jobs for both
source kinds — tara-instructor-class and saraswati-artist-speech — each
targeting a second locale, clearing a GA quality floor of
V3_VOICE_DUBBING_PIPELINE_MIN_GA_QUALITY_SCORE = 0.95, and passing
translation, pronunciation, timing, and subtitle review plus a rights gate
(voice consent, signature, scope lock, signoff, royalty route) and an
adversarial policy gate with zero breaches. The cultural-adaptation review
(buildV3CulturalAdaptationReviewReport) gates tradition-sensitive Tara and
Commons content through a documented process (anchored to a real
V3/CULTURAL_ADAPTATION_REVIEW.md reference and an ISO-timestamped signoff),
covering all launch locales with five required review roles, an adaptation
brief, a protected-terms glossary, documented prohibited claims, and an approved
release signoff applied to GA inventory — so a localized dub cannot flatten
lineage-specific material. These engines are what the festival programming and
venue content lean on; see
./commons-venues-and-live-service.md.
The shared @oshun/i18n spine#
Underneath the V3 in-world layer sits V1's @oshun/i18n (libs/oshun/i18n, 721
lines) — the canonical catalog for shared customer-facing shell strings. It is a
distinct, narrower layer: 8 V1 launch locales (en-US, es-US, fr-FR,
de-DE, ar, he, ja-JP, pt-BR) covering 26 cross-shell message keys
(assistant.disclosure.ai, evidence.provenance.open, privacy.dsar_received,
…), each fully translated, not the 18-locale in-world HUD set. What makes it
load-bearing for V3 is the machinery the in-world layer reuses: a documented
translate() fallback chain (requested → language-only → en-US) that records
a fallbackApplied flag and emits a TranslationFallbackEvent so a missing
string logs a gap rather than rendering empty; findFallbackGaps(), which
enumerates every <key, locale> pair still resolving via fallback so a
pre-launch audit can prove zero gaps; an RTL_LOCALES set and localeDirection
helper; per-locale text-expansion budgets (de-DE 1.4×, ja-JP 0.7× relative
to en-US) that layout QA verifies; and real Intl.*-based date, currency,
number, and relative-time formatting per locale. The V3 promise that "a missing
string falls back to US English with a logged gap" is this code, exactly.
Telemetry, eval, and quality#
The versioned telemetry schema#
V3 telemetry rides V1's analytics package and Grafana stack — it adds schemas
and boards, not infrastructure. v3-telemetry-schema.ts (published as
schema-v3) defines versioned events with an eventId, surface, consent
mode, and non-PII property definitions: oshun.accessibility.mode_changed
(e.g. mode: keyboard_single_switch, state: enabled),
oshun.localization.locale_selected (rtl_layout, voice_dub_available,
subtitle_anchor_available), oshun.tier_router.decision_recorded,
oshun.safety.report_submitted, and saraswati.music_sync.drift_measured.
Trace IDs are intended to span a full session lifecycle, emitted over
OpenTelemetry; the world server is the proof point, with its OTLP exporter
opt-in behind V3_WORLD_SERVER_OTEL while Prometheus scraping needs no flag.
The eight tier-stack dashboards (v3-grafana-dashboards.ts) each carry a named
PagerDuty rotation and an alert that fires when its gate is not green.
Eight eval-set release gates, fail-closed#
The heart of "quality" is v3-eval-sets-release-gates.ts: eight required eval
surfaces, each with a hard threshold and a direction of min or max.
| Surface | Metric | Gate | Observed |
|---|---|---|---|
| World-server tick | snapshot_p99_ms |
≤ 25 ms | 18.4 ms |
| Gateway latency | browser_pop_rtt_p99_ms |
≤ 60 ms | 44 ms |
| Voice latency | mouth_to_ear_rtt_p95_ms |
≤ 80 ms | 58 ms |
| Music-sync drift | cross_attendee_drift_p95_ms |
≤ 25 ms | 19 ms |
| Aja accuracy | canonical_asana_min_per_asana_accuracy |
≥ 0.92 | 0.94 |
| Persona-policy | adversarial_policy_breach_count |
≤ 0 | 0 |
| Crisis routing | crisis_routing_recall |
≥ 1.0 | 1.0 |
| Harassment SLA | harassment_report_action_p95_minutes |
≤ 45 min | 31 min |
buildV3PerSurfaceEvalSetReport and evaluateV3ReleaseGateEvalRun implement
the threshold logic directly
(direction === 'min' ? value >= threshold : value <= threshold) and fail
closed: publication is rejected if a required surface is missing, a threshold
is absent, an evidence path is missing, the CI state is not green, or any
observed metric misses its bar — a needs_data state for any missing metric,
red for any breach. The committed runs all pass, but the manifest is wired so
a regressed Aja accuracy or a single persona-policy breach cannot be papered
over at release. The eval gate is one constituent of the launch gate, not a
standalone green.
The WCAG gate and the honestly-red launch gate#
Accessibility folds into one machine-checked artifact too:
v3-testing-qa-accessibility.ts is a fail-closed QA gate whose checklist
includes 70.vitest-ts-coverage, 70.axe-web-2d,
70.motion-photosensitive-audio (wiring verify:v3 reduced-motion-mode,
verify:v3 photosensitive-safe-mode, verify:v3 spatial-audio-off-mode),
keyboard navigation, and a screen-reader audit, emitting
V3/qa/testing-qa-accessibility-release-gate.json (status green): Vitest at
84.6% meaningful-line coverage, Playwright on chromium+webkit with zero
serious/critical axe violations, and the WCAG 2.2 AA modes at 100% coverage with
a signed-off artifact.
All of this feeds the §75 launch-readiness gate, which is the output of
scripts/v3/verify-v3-launch-readiness.mjs executing every constituent verifier
and ANDing the results. Today it reads status: red, passingGateCount: 30 of
41, gaReady: false — the correct, load-bearing state for a pre-GA build, with
the eval and accessibility gates green and the still-open gates (Pixel Streaming
fleet 5×, cross-platform certification, GA-inventory rollups) honestly red. That
red is the point: the gate is wired so its green is unforgeable. The full
launch-and-rollout machinery is covered in
./operations-rollout-and-launch.md, and
the observability, eval, and gate architecture in
../architecture/observability-performance-security-and-launch.md.