Skip to content

Decide whether a visualizer that turns available again gets a join trigger #26

Closed

Description

@kahrendt

Problem

PlayerStream returns a JOIN trigger when its client turns available again; VisualizerStream returns none, although its eligibility depends on availability. No comment says whether the asymmetry is intended. Their on_activation_written() bodies are also identical.

Evidence

Checked against the code at e69f3b46a801:

  • PlayerStream::on_activation_written (player_stream.cpp:47-64) and VisualizerStream::on_activation_written (visualizer_stream.cpp:52-70) are textually near-identical: the same added/role_written_/state_since_activation_=false/JOIN logic, and the same drop of sent_, ledger_, cursor_ and announce_.
  • PlayerStream::on_client_state returns JOIN when available turns true (first report, or after being unavailable) (player_stream.cpp:66-86). VisualizerStream::on_client_state just assigns available_ and has no state_received_ or was_available tracking (visualizer_stream.cpp:72-84).
  • VisualizerStream::eligible() (visualizer_stream.h:130) does depend on available_, so a visualizer returning to available gets no JOIN trigger. Session::on_client_state (session.cpp:392-394) then queues the marker with kind nullopt. VisualizerStream::bind_cursor defaults pending_kind_ to GROUP, so startup_lead_us gives only join_min_us rather than the group lead (visualizer_stream.cpp:365-380). Neither the code nor docs/internals.md explains this asymmetry (grep found nothing).
  • Session::on_activation_taken (session.cpp:801-806) calls artwork_ and source_role_ with role_active(string), and player_ and visualizer_ with roles.contains(StreamRole).

Checked and not part of this issue

  • 'Line for line' is true only for on_activation_written, and not exactly there: the visualizer version also resets offset_ (visualizer_stream.cpp:64) and the player version does not (player_stream.cpp:56-62).
  • prepare, announce_due, commit_announce, bind_cursor and cursor_ring are not line for line. VisualizerStream::prepare handles in-place configuration announcements, usable_wanted and on_stream_changed. PlayerStream::prepare handles follow_wanted and switch_ formats. Visualizer cursor_ring and ring_of are keyed by VisualizerConfig, and bind_cursor resets offset_ and uses its own startup_lead_us (visualizer_stream.cpp:365-380). Player bind_cursor uses the PlayerLead and InputKind (player_stream.cpp:482-488).
  • set_support also differs: the player constructs Ledger(cap, true) and sets output_delay, the visualizer constructs Ledger(cap, false).
  • ArtworkStream's 'returned' is not a drifted copy of the player logic. It clears left_ only when the client was previously received and unavailable (artwork_stream.cpp:63-67). That is the artwork role's own stream-lifecycle semantics (it has left_ and no cursor or trigger kind), so it is not comparable.
  • Two role-identification schemes is a deliberate split, not drift. sched::StreamRole/RoleSet (sched/types.h:81-107) covers only the roles whose streams the cursors and markers carry (PLAYER, VISUALIZER) and is what the queues use (queues_.head_roles, roles_ahead, roles_written_). Artwork and source have separate stream objects with no cursor markers and use the string-based role_active (session.cpp:515, 801-802).

Direction

Decide (owner) whether the visualizer should trigger a join, then fix it or comment the asymmetry. Optionally share the identical activation code.

Done when

  • The behavior is decided and either tested or commented at the asymmetric site.

Activity

  1. self-assigned this
    on Oct 16, 2026
  2. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    Claimed 4fa604ac; branch issue-26-visualizer-available-join

  3. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    Rescoped

    The question this issue asks is answered: yes, a visualizer that turns available again joins, and it already does. The evidence above missed the path that gives it the kind.

    Why the premise was wrong

    Checked against the code at 655a5db:

    • PlayerStream::on_client_state returns JOIN when available turns true whether or not the player role is active (player_stream.cpp:74-76), so it tracks availability for every client.
    • Session::on_client_state takes player ? player : visualizer (session.cpp:408-410), and set_pending_kind() sets the kind on both streams (session.cpp:631-637). A visualizer-only client's marker carries JOIN, and VisualizerStream::startup_lead_us gives it the group lead.
    • The spec gates every stream/start on available: true (messaging.md "Server -> Client: stream/start"), whatever the role; the player and source wording in "Client -> Server: client/state" only adds the clock-sync precondition for those two. aiosendspin's visualizer joins on turning available too (server/roles/visualizer/v1.py, on_availability_changed).

    What is left

    1. Client-level availability lives in PlayerStream. ServerCore reads it for every client as session().player().available() (server_core.cpp:713), and PlayerStream::on_client_state's doc says the turn "made the player eligible" (player_stream.h:68-69). That placement is what made the asymmetry look like a bug.
    2. No test covers a visualizer-only client turning available while its group plays. ServerVisualizer's availability test (test_server_visualizer.cpp:560-567) only checks that a client back in a stopped solo group gets nothing.
    3. Optional: the two on_activation_written() bodies are near-identical.

    Direction

    Move the availability tracking (available_, state_received_) and the turn-to-available JOIN from PlayerStream into Session, have ServerCore read availability from the session, and keep each stream's eligible() fed from it. Share the activation code if it falls out naturally.

    Done when

    • Availability and its JOIN are decided in one place outside PlayerStream, and no comment ties them to the player role.
    • A test has a visualizer-only client whose first client/state reports available: false sit in a playing group, then report true and get a visualizer stream/start at the group lead.
  4. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    Released 4fa604ac; resumed at the owner's request

  5. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    Claimed 1c47c52c; branch issue-26-visualizer-available-join

  6. kahrendt commented on Oct 16, 2026

    @kahrendt
    ContributorAuthor

    PR: #98

  7. added a commit that references this issue on Oct 16, 2026
    a88fc9d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions