Skip to content

Replace SSSpeechSynthesizer with testable MUDSpeechQueue skeleton#16

Open
powerje wants to merge 1 commit into
trunkfrom
speech-queue-skeleton
Open

Replace SSSpeechSynthesizer with testable MUDSpeechQueue skeleton#16
powerje wants to merge 1 commit into
trunkfrom
speech-queue-skeleton

Conversation

@powerje

@powerje powerje commented Apr 11, 2026

Copy link
Copy Markdown
Member

Summary

Phase 2 foundation for the VoiceOver speech-pipeline rebuild. Replaces the 2013 SSSpeechSynthesizer Obj-C class from Vendored/SSAccessibility with three new Swift types under src/Mudrammer/Accessibility/:

  • SpeechLine — struct carrying text, priority (iOS 17+ UIAccessibilityPriority), and origin (.server / .prompt / .trigger / .system). Only text is load-bearing today; the other fields exist so follow-up work can populate them without reshaping the type.
  • Announcer — protocol with a single announce(_:completion:) method that abstracts the "post to VoiceOver" step. Production implementation UIAccessibilityAnnouncer posts an NSAttributedString with accessibilitySpeechAnnouncementPriority and accessibilitySpeechQueueAnnouncement attributes, observes UIAccessibility.announcementDidFinishNotification for completion, with a 16s timeout fallback matching the 2013 implementation's behavior.
  • MUDSpeechQueue@objc-exposed class that holds an internal FIFO buffer and drains one line at a time through the injected Announcer. Exposes enqueueLineForSpeaking: and stopSpeaking for one-line drop-in replacement at the SSMudView call site.

SSMudView.m is updated to construct [MUDSpeechQueue new] instead of [SSSpeechSynthesizer new] and no longer imports SSAccessibility. The unused continueSpeaking method (declared in SSMudView.h but only self-referentially called from SSMudView.m) is removed — a grep across the tree found no external callers.

The 2013 Vendored/SSAccessibility package is left in place for now; it has no remaining call sites in app code and will be deleted in a follow-up cleanup step once the migration settles.

Why this matters

Research in the prior session surfaced that the #1 real-world complaint from blind MUDRammer users in the 2025 AppleVis thread is prompt-jumping — the 2013 SSSpeechSynthesizer posts at default priority, which interrupts current speech mid-sentence whenever a new line arrives. Posting at .low priority (queued behind existing speech, yielding to system announcements) is the direct fix, and iOS 17's UIAccessibilityPriority is the first-party primitive that makes it trivial. The Announcer seam is what lets future work unit-test the queue without relying on a real VoiceOver subsystem that can't be observed from tests.

Deliberately out of scope

This is a skeleton. Each item below is a separate follow-up PR:

  • Pre-enqueue filter pipeline (blank / whitespace / ASCII-art)
  • Prompt detection and prompt filter (the actual fix for the prompt-jumping complaint)
  • User-configurable interrupt-vs-queue mode
  • Observable drop accounting / delegate
  • User-typing behavior hook
  • Per-world speech configuration persistence
  • Deletion of Vendored/SSAccessibility
  • Priority-semantics spike on iOS 26 under MUD-rate bursts
  • SSANSIEngine preprocessing test coverage

Test plan

  • xcodebuild test -workspace Wammer.xcworkspace -scheme MRTests -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -only-testing:MRTests/SpeechQueueTests — expect 6 passing tests in ~0.2s
  • xcodebuild build -workspace Wammer.xcworkspace -scheme Wammer -destination 'platform=iOS Simulator,name=iPhone 17 Pro' — expect clean build with no new warnings
  • Open Wammer.xcworkspace in Xcode, build the Wammer target, verify the audit navigator shows no regressions
  • On a physical iPhone with VoiceOver enabled, connect to a test MUD, confirm incoming text is still spoken and stopSpeaking fires on disconnect/clear/user input/radial-movement (the four [self stopSpeaking] call sites in SSMudView.m)

Known pre-existing issues not addressed here

The MRTests scheme has a pre-existing flake in GMCPHandlerTests.reset where an auto-layout call runs on a background thread and aborts the test process. Verified present on trunk independently of these changes — not introduced by this PR.

Unit test coverage

src/MRTests/SpeechQueueTests.swift uses a RecordingAnnouncer test double that captures every announce call and exposes a manual completeOldest operation, so each test drives the queue's drain loop deterministically:

  • emptyTextIsDroppedAtEnqueue
  • nonEmptyLineReachesAnnouncer
  • objcEnqueueUsesLowPriorityAndServerOrigin
  • linesDrainInOrderAsCompletionsFire
  • stopSpeakingClearsPendingLinesNotYetAnnounced
  • enqueueAfterStopSpeakingWorksNormally

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant