{
  "version": 1,
  "capturedAt": "2026-10-13T03:43:10.708971+00:00",
  "rule": "Preserve the 201 features accepted in the confirmed ballot. For newer projects, queue ideas with more Want votes than Maybe and Skip combined. Previously excluded ideas require a new acceptance decision.",
  "entries": [
    {
      "mod": "xivpiano",
      "id": "piano-boss-themes",
      "title": "Let the Boss Theme Play",
      "pitch": "Pick duties where the game's own music matters, and XivPiano steps aside there: the radio pauses and the game's music comes back.",
      "how": "In Plugin.OnUpdate, beside the cutscene pause, a 'step aside' check: ICondition BoundByDuty plus the duty's ContentType (from IDutyState and the ContentFinderCondition sheet) against a set in Configuration. It pauses with Radio.TogglePause like pausedForCutscene does, with its own flag. The music muting needs no change: OnUpdate already puts IsSndBgm back as it was whenever the radio stops playing.",
      "risks": "A paused song's audio link can expire during a long raid, so resuming may skip to a fresh song; Radio already does that quietly. Two reasons to pause (a cutscene inside a duty) must not resume each other too early, so the flags need to be kept apart and tested.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 1
    },
    {
      "mod": "xivpiano",
      "id": "piano-history-window",
      "title": "What Was That Song?",
      "pitch": "A list of the last twenty songs, with thumbs, bookmark and 'why this song?' still working on them after they have finished.",
      "how": "Radio already keeps the last Radio.HistoryLength (20) finished tracks in Radio.History. A History tab in MainWindow lists them with the same icon buttons as DrawControls; thumbs call PandoraClient.AddFeedbackAsync with the old track's StationId and Token (pianobar rates songs from its history the same way), and bookmarks and ExplainAsync take the Token as they do now. Album art reuses LoadArtAsync.",
      "risks": "A thumb on an old song must go to the station it played on, which may not be the current one; Track.StationId has it, but the station token must be looked up. Pandora may refuse feedback on a very old track token, and the error has to read plainly. History is lost when the game closes unless it is saved.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 2
    },
    {
      "mod": "xivarcade",
      "id": "arcade-bgm-duck",
      "title": "Hush the Orchestrion",
      "pitch": "While an arcade panel has the focus, FFXIV's music fades down so the chiptune is the only soundtrack, and it fades back when you look away.",
      "how": "PadCaptureService already decides five times a second, from GhosttyWire.FocusGet, whether the arcade panel has the focus. Feed the same signal to a small AudioDuck service that reads SoundBgm through Dalamud's IGameConfig once, lowers it in steps on the framework tick, and restores the remembered value on focus loss, every PadRelease reason and Dispose. A Configuration flag (on by default) and a /arcade duck toggle.",
      "risks": "IGameConfig writes land in the player's real config, so a crash mid-duck leaves the music quiet on next launch; store the original in the plugin config and restore it on load. Players who already muted BGM must not have it turned back up. Focus detection depends on ghostty-dalamud and is not yet seen in game.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 3
    },
    {
      "mod": "xivdesktop",
      "id": "desk-privacy-veil",
      "title": "The Veil",
      "pitch": "One chord, or GPose starting, hides private panels and blanks window titles in the palette, taskbar and notifications.",
      "how": "A Veiled flag in SessionService, set by a new KeyAction and by IClientState's GPose state. When set, PaletteEngine gets a context with titles masked, the Windows gate returns masked titles to the Umbra widget, SessionService.Notify drops names, and panels tagged private in Configuration are hidden through window.hide.",
      "risks": "XivDesktop cannot mask the contents of a panel it hides late: anything captured in the first frame is already out. Other plugins drawing window titles are not covered. It must be clear this reduces exposure, not a guarantee.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 4
    },
    {
      "mod": "xivmcp",
      "id": "mcp-objective-here",
      "title": "Pin This Spot as an Objective",
      "pitch": "`/xivmcp quests here \"Title\"` records your zone, map coordinates, the current bell and weather as a new objective, and `export` writes the lot as a pack to share.",
      "how": "ObjectiveCommands.cs gains 'here' and 'export' next to its ten verbs. 'here' reads the territory and player position as get_location does, converts to map x/y with the same maths as CoordinateProvider and set_map_flag, reads the live weather from WeatherManager and the Eorzea bell from the clock EorzeaTimeWindow uses, then builds the objective through ObjectiveFactory. 'export' serialises the store in the ObjectivePack format that load_objective_pack reads.",
      "risks": "What time window to record is a guess: the current bell alone is too narrow, so it defaults to no time condition and says so. Weather in fixed-weather zones is reported as a problem by the evaluator, which is correct but surprising. Export paths under Wine need the same Z: mapping as load.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 5
    },
    {
      "mod": "xivmcp",
      "id": "mcp-tickets-in-duty-list",
      "title": "Pending Approvals Where You Already Look",
      "pitch": "Open approval tickets show as a single tracked line under the Duty List, so a request filed while you were away is noticed without opening the plugin window.",
      "how": "APPROVALS.md names the hook: ApprovalQueue.Changed and Snapshot(). ObjectiveOverlay reads the pending count and the first two summaries (the existing approval sentences) and draws one entry; its click calls the same toggle as the XivMcp.ToggleWindow IPC gate with the Approvals tab selected.",
      "risks": "Approving from a Duty List row would be one mis-click from an unintended teleport, so the row only opens the tab, and that has to stay true. Ticket reasons are client text and are shown only in the tab, not the overlay. Same unverified overlay placement as objectives.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 6
    },
    {
      "mod": "xivpiano",
      "id": "piano-duck-dialogue",
      "title": "Quieter While Someone Speaks",
      "pitch": "Lower the radio while a dialogue box is open, and bring it back up when the talking is over, instead of only pausing for full cutscenes.",
      "how": "An IAddonLifecycle listener on the Talk addon (PostSetup and PreFinalize) sets a 'ducked' flag in Plugin. AudioOut.Volume is ramped over about 300 ms toward Config.Volume times a duck factor (Configuration.DuckTo, default 25%) from Plugin.OnUpdate, and back when the flag clears. AudioOut already applies Volume per sample in MpegSampleProvider.Read, so the ramp is a few lines.",
      "risks": "The plugin cannot tell a voiced line from a silent one, so it ducks for every dialogue box; that has to be a setting. The ramp must never fight the volume slider in MainWindow. Talk is not the only dialogue addon (battle talk, some quest windows), so the first version will miss some.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 7
    },
    {
      "mod": "xivpiano",
      "id": "piano-share-song",
      "title": "Share What's Playing",
      "pitch": "One button copies a line about the song, with its Pandora link, ready to paste into party, FC or linkshell chat.",
      "how": "A Share button in MainWindow.DrawControls builds the line from Radio.Current (Title, Artist, Album and DetailUrl, Pandora's page for the song, which Track already carries) and puts it on the clipboard with ImGui.SetClipboardText. A matching '/piano share' copies it too. Nothing is sent by the plugin: the player pastes it into whichever channel they choose.",
      "risks": "Automatically posting to party or FC chat would be a plugin sending chat for you, which Square Enix's terms and Dalamud's rules frown on, so this only fills the clipboard. Some players will find the extra paste a nuisance, and that is the price. DetailUrl can be empty; the line must read well without it.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 8
    },
    {
      "mod": "xivwayfinder",
      "id": "way-named-places",
      "title": "Places You Keep",
      "pitch": "Save where you stand under a name and point back at it later: /wayfinder save forge, /wayfinder go forge.",
      "how": "WayfinderCommands.Parse gains save, go, forget and places verbs (they must be matched before the free-text zone words). Configuration stores a list of (name, territory, x, y, z); names compare through ZoneMatch.Fold so accents and case do not matter. 'go' sets explicitTarget exactly as SetMapTargetFromCommand does, with the name as the label, so arrival, the aetheryte hint and GetState all work unchanged.",
      "risks": "A name that is also a zone name ('/wayfinder go limsa') needs a clear rule. Positions inside instanced housing are per ward and plot and would need those stored too, or be refused.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 9
    },
    {
      "mod": "almanac",
      "id": "almanac-duty-quiet-mode",
      "title": "Quiet While You Pull",
      "pitch": "Almanac hides its window in combat and duties, holds any reply that finishes meanwhile, and tells you with a toast once you are out.",
      "how": "Inject ICondition and IToastGui into the Plugin constructor beside ICommandManager and IFramework. On IFramework.Update, watch ConditionFlag.InCombat and BoundByDuty; set ChatWindow.IsOpen false and remember it was open. ChatSession keeps running on the thread pool as it does now; when it goes from Busy to idle during quiet mode, queue one toast for when the flags clear. A setting in SettingsWindow turns it off.",
      "risks": "Some players want the window open in duties, such as for a crafting-log duty or a trial they have cleared many times, so it must be optional. Toast wording should not look like a game system message. Condition flags change briefly during cutscenes and zone loads and need a short debounce.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 10
    },
    {
      "mod": "almanac",
      "id": "almanac-thread-voices",
      "title": "A Voice per Thread",
      "pitch": "Give each thread its own system prompt, so the crafting thread stays terse and the lore thread can talk like a Sharlayan librarian.",
      "how": "AlmanacSettings.SystemPrompt is one global string and Engine.GetChatSession passes a Func<string> for it. Store a per-thread prompt in the kv table (thread.<id>.prompt) so no migration is needed, have ChatSession.SendAsync ask for the thread's prompt before the global one, and copy it in AlmanacStore.ForkThread. A few built-in voices ship beside DefaultSystemPrompt; the thread context menu in ChatWindow.DrawThreads picks one.",
      "risks": "A voice that drops the default prompt's rules ('do not guess such facts', 'if an action is denied, do not retry') makes a small model worse at tools, so custom voices should be appended to those rules, not replace them. Voices that imitate named NPCs are fan content; keep the shipped ones generic.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 11
    },
    {
      "mod": "xivarcade",
      "id": "arcade-play-journal",
      "title": "Play Journal",
      "pitch": "Hours and sessions per game, from the play history the helper already records, shown on each cover and in a weekly chat line.",
      "how": "The plays table in arcade.db already stores game_id, core, started and ended for every launch. build_state adds total seconds and session count per game to state.json; ArcadeGame gains two fields; ArcadeWindow's details strip prints them next to last played. A 'journal' verb in ArcadeCommands prints the summary to chat.",
      "risks": "arcade.db is per machine, so hours played on the laptop are missing on the desktop unless a small per-machine journal file is put into the synced tree and merged. A crashed emulator leaves ended empty, so sessions need a sensible cap.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 12
    },
    {
      "mod": "xivdesktop",
      "id": "desk-record-chord",
      "title": "Press the Chord to Bind It",
      "pitch": "Record a key chord by pressing it in Settings, and be told straight away if Wine never delivered it.",
      "how": "KeybindService already reads IKeyState with a GetAsyncKeyState fallback; add a capture mode that suspends ChordTracker, waits for the first non-modifier key and builds a KeyChord from the held modifiers, then writes it to Configuration.Keybinds through the existing SettingsWindow Keybinds tab. The timeout message is the useful part: it is the first direct evidence of whether Wine passes the modifier at all.",
      "risks": "The game still acts on the keys while recording, so a chord bound to a game action fires once. Some compositors only drop Super on release. Cannot see what the game has bound without reading keybind data, so the conflict hint stays heuristic.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 13
    },
    {
      "mod": "xivdesktop",
      "id": "desk-scratchpad",
      "title": "The Chocobo Saddlebag Scratchpad",
      "pitch": "A sway-style scratchpad: stow any panel out of sight with one chord and summon it back to your shoulder with another.",
      "how": "Add a scratchpad as workspace 0 in WorkspaceModel (outside Count's 1-9), so Plan hides its panels regardless of the current workspace. Two new KeyAction entries in Core/Input/Keybinds.cs with defaults, handled in CommandRunner; summoning uses window.hide off plus window.toggle_pet or window.place with the configured PinArgs.",
      "risks": "Needs window.hide from the newer Ghostty methods; on older backends it falls back to minimise or is unavailable. Workspace persistence must not treat 0 as an orphan to clean up.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 14
    },
    {
      "mod": "xivlantern",
      "id": "lantern-dtr-roster",
      "title": "Glance at the Server Bar",
      "pitch": "Hover the Lantern entry in the server info bar to see who is lit and their notes, without opening the window.",
      "how": "Notifier already owns the IDtrBarEntry and refreshes its text on a timer from service.Friends and Book.TotalUnread. Set the entry's Tooltip to a short roster built by a pure function in XivLantern.Core (sorted online first, names through ChatText.Label, notes through ChatText.Preview) so it is host-tested. Clicking still calls toggleWindow.",
      "risks": "Tooltips are SeString, so friend-typed names and notes must be escaped so they cannot inject payloads or colour codes. Long lists need a cap ('and 7 more'). Hidden while streaming would be a sensible option, since the bar is often on screen.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 15
    },
    {
      "mod": "xivlantern",
      "id": "lantern-safety-words",
      "title": "Check It Is Really Them",
      "pitch": "Four short words derived from both of your node ids that you can read to each other in game or in voice to confirm the friend is who they say.",
      "how": "A pure function in XivLantern.Core over the two 32-byte node ids (sorted, hashed, mapped to a fixed wordlist), host-tested for symmetry. MainWindow's friend context menu shows it; a local Verified flag in Configuration adds a mark in DrawList. Display names are self-asserted (DESIGN.md), and friendship is keyed on the QUIC-authenticated NodeId, so this checks the thing that is actually authenticated.",
      "risks": "Only as good as the out-of-band check; people will tick it without comparing. A friend who reinstalls gets a new NodeId and must be verified again, which is correct but will surprise them.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 16
    },
    {
      "mod": "xivpiano",
      "id": "piano-genre-stations",
      "title": "Browse Pandora's Genre Stations",
      "pitch": "A list of Pandora's own genre stations by category, to start one with a click when you do not know what you want.",
      "how": "PandoraClient.GetGenreStationsAsync calls station.getGenreStations and returns categories of (name, stationToken). A 'Genres' tab in MainWindow lists them; picking one runs Radio.CreateAndPlayAsync with PandoraClient.CreateStationAsync on that genre station's token, the way pianobar makes a station from a genre. The list is cached for the session.",
      "risks": "Pandora has changed its genre catalogue over the years and this call may return fewer genres than the web app shows. Each pick adds a station to the list, and Pandora caps how many stations you may have (PandoraException.StationLimit already explains it).",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 17
    },
    {
      "mod": "xivpiano",
      "id": "piano-song-events",
      "title": "Other Plugins Hear the Song Change",
      "pitch": "Tell other plugins the moment the song changes, so XivMcp, Ghostty or a Stream Deck setup can react without asking every second.",
      "how": "A message gate XivPiano.SongChanged next to the existing XivPiano.NowPlaying and XivPiano.Control, sent with ICallGateProvider.SendMessage from Plugin.OnUpdate where announcedToken already notices a new track, carrying the same JSON NowPlayingJson builds. XivMcp could add read and control tools that call NowPlaying and Control. Stream Deck works today through macros with /piano; the event is for plugins that show state.",
      "risks": "Subscribers run on the framework thread, so a slow one would stall the game; the docs must say to return quickly. The JSON shape becomes a contract to keep. XivMcp's control tools would need the same in-game approval as its other actions.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 18
    },
    {
      "mod": "xivwayfinder",
      "id": "way-corner-double-tap",
      "title": "The Glove Warns of a Turn",
      "pitch": "With vnavmesh, the glove taps twice and leans into the next leg a few yalms before the path turns a corner.",
      "how": "Navmesh already exposes Corner and the path's corners. In Overlay.DrawWalk, when the flat distance to path[Corner] falls under about 3 yalms, blend the glove's angle towards the next segment and run Pulse.Tap at double rate for one period. PathFollow gains a small pure helper for 'approaching corner' so it can be host-tested next to MotionAndPathTests.",
      "risks": "vnavmesh paths in towns have many short corners; the glove could fidget. Needs a minimum turn angle (say 30 degrees) before it warns. Not yet seen in game, like the rest of the path code.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 19
    },
    {
      "mod": "xivwayfinder",
      "id": "way-source-colours",
      "title": "A Colour for Every Source",
      "pitch": "The bead takes a different colour for your own target, the map flag and the quest step, so you always know which one it is following.",
      "how": "Configuration gains a colour per TargetSource beside BeadColor (which stays the default for Explicit). Overlay.DrawBead and DrawTrail pick the colour from scene.Guide.Target.Source; a Pulse.Approach blend over about 0.4 s eases the change. SettingsWindow gets three colour pickers and a 'one colour for all' box. Colors.cs already has Pack and Lighten.",
      "risks": "Three more colours to explain in a window that is already busy. Red and green as defaults would fail colour-blind players, so the defaults should differ in lightness too.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 20
    },
    {
      "mod": "almanac",
      "id": "almanac-reference-pages",
      "title": "Reference Pages",
      "pitch": "Save a question you ask often as a named page and run it with one command or macro line, so your daily checks are one keypress.",
      "how": "Store pages in the kv table under page.<name>. Plugin.OnCommand gains a 'page <name>' case that opens a fresh thread through ChatSession.New and sends the stored text via ChatWindow.Send. A small list in SettingsWindow edits them, and the transcript's context menu gets 'Save this question as a page'. Because it is a slash command it can sit in a game macro.",
      "risks": "A page that asks for an action-tier tool will still stop for XivMcp's approval, which is correct but surprising in a macro. Pages run one at a time; a second while Busy is refused, as Almanac.Ask is today. It must never become a timer that runs itself.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 21
    },
    {
      "mod": "xivdesktop",
      "id": "desk-desktop-actions",
      "title": "Right-Click Actions from the .desktop File",
      "pitch": "Show each app's own [Desktop Action] entries, such as a new private window or a compose message, as palette row actions.",
      "how": "DesktopEntryParser ignores every group but [Desktop Entry]; parse the listed Actions groups into DesktopEntry.Actions (name, Exec, Icon) and run each Exec through ExecLine like the main one. PaletteEngine.RowActions appends them after the built-in actions; LauncherWindow's context menu lists them under Launch. AppSummary gains an optional actions list, an additive change to IpcContract.",
      "risks": "Chord assignment for arbitrary actions can collide with existing row keys, so only the first few get one. Some apps put odd things in actions that assume a running session bus.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 22
    },
    {
      "mod": "xivdesktop",
      "id": "desk-macro-lines",
      "title": "Apps on Your Hotbar, by Macro",
      "pitch": "A palette action that copies a ready /desktop launch line for any app, so it can live in a user macro on your hotbar.",
      "how": "PaletteCommand.Copy already exists and LauncherWindow already has 'Copy command'. Add a RowAction in PaletteEngine.RowActions that copies '/desktop launch <desktop-file id>'; the plugin's command handler already resolves ids first. The id is stable across rescans, unlike the name.",
      "risks": "XivDesktop does not write the macro itself; touching the game's macro data is out of bounds, so the player pastes by hand. Macros are shared per character and can be synced; an id for an app you later remove just fails with a message.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 23
    },
    {
      "mod": "xivlantern",
      "id": "lantern-friend-groups",
      "title": "Friend Folders",
      "pitch": "Sort friends into your own folders (static, FC, crafting pals) that collapse and show how many in each are lit.",
      "how": "Groups are local only: a Dictionary<string, string> from node id to folder in Configuration, never sent to anyone. MainWindow.DrawList renders ImGui.CollapsingHeader per folder with an online count from service.Friends, and a right-click menu on a friend to move them. Mutes keep using the existing Muted keys, so a folder can be muted in one step by toggling each key.",
      "risks": "Folders keyed by node id split when a friend reinstalls (new NodeId), until nostr device lists merge devices into one person. Drag and drop in Dalamud ImGui is fiddly; a context menu is the safer first cut.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 24
    },
    {
      "mod": "xivlantern",
      "id": "lantern-idle-away",
      "title": "Away When You Walk Off",
      "pitch": "Opt in, and your lantern dims to away after a few minutes with no keyboard or mouse input, and lights again when you come back.",
      "how": "A Framework.Update tick in LanternService (it already polls lt_poll there) checks OS idle time through GetLastInputInfo, never game state, and calls SetPresence with LT_PRESENCE_AWAY once past a threshold set in Configuration, restoring the previous presence and note on input. Off by default, shown in DrawSettings with the other switches.",
      "risks": "GetLastInputInfo under Wine reports X11/Wayland input only as well as Wine forwards it; controller-only players may look idle while playing. It must never override a status the player set by hand (busy, invisible), and the banned-API test in CoreTests must keep passing, so no IClientState shortcuts.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 25
    },
    {
      "mod": "xivlantern",
      "id": "lantern-status-presets",
      "title": "Status Presets",
      "pitch": "Save a handful of status-and-note pairs and switch between them with one click or one short command.",
      "how": "Add a List<StatusPreset> (name, presence, note) to Configuration. Command.Parse gains an 'as <preset>' verb beside ParseStatus; Plugin.OnCommand resolves it and calls the existing LanternService.SetPresence. MainWindow.DrawMe gets a small preset combo next to the presence combo and note field. Parsing is BCL-only, so it is host-tested in XivLantern.Core.Tests like the rest of Commands.",
      "risks": "Very little. Preset names must not collide with existing subcommands (on, off, invite), so they sit behind 'as'. Notes are still typed text and are clamped by the same length limit.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 26
    },
    {
      "mod": "xivmcp",
      "id": "mcp-client-budget-meter",
      "title": "Per-Client Call Meter",
      "pitch": "The Status tab shows each connected client's calls per minute against the rate limit, and the long-missing rate-limit control lands under Advanced.",
      "how": "TODO.md lists it: RateLimitPerMinute exists with no UI; add the input under Advanced in MainWindow.Settings.cs. The per-client bucket McpServer already keeps for RateLimitPerMinute and RateLimitBurst exposes its current fill, read by MainWindow.Status.cs as one bar per client, and get_server_info gains the same numbers.",
      "risks": "Client names are self-reported unless a per-client token is used, so two clients can share a bar. Reading the buckets from the draw loop must not take the same lock the dispatch path holds.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 27
    },
    {
      "mod": "xivpiano",
      "id": "piano-explicit-filter",
      "title": "Explicit Content Filter",
      "pitch": "Turn Pandora's explicit content filter on or off from XivPiano's settings, for streaming or a shared room.",
      "how": "PandoraClient gains GetSettingsAsync (user.getSettings, reading isExplicitContentFilterEnabled) and ChangeSettingsAsync (user.changeSettings with isExplicitContentFilterEnabled), as pianobar's settings menu does; changeSettings wants the current username and password, which PandoraClient keeps for the session in its credentials field. A checkbox in MainWindow.DrawSettings, read after sign-in. Queued songs are dropped after a change so the filter applies from the next song.",
      "risks": "It is an account setting, so it changes the Pandora app and website too. If the password was not kept, a change needs it typed again. Songs already fetched still play until the queue is refilled.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 28
    },
    {
      "mod": "xivpiano",
      "id": "piano-quickmix",
      "title": "Choose What Shuffle Mixes",
      "pitch": "Tick which stations Shuffle draws from, the way Pandora's QuickMix lets you.",
      "how": "Station already knows IsQuickMix. PandoraClient.GetStationsAsync would also read quickMixStationIds from the QuickMix station in the user.getStationList answer, and a new SetQuickMixAsync sends user.setQuickMix with the chosen quickMixStationIds, as pianobar does. MainWindow.DrawStations shows a checkbox per station when 'Edit Shuffle' is on.",
      "risks": "Changes apply to the account everywhere, including the Pandora app, which is the point but should be said. The field is not present for every account type, so the checkboxes must hide when it is missing.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 29
    },
    {
      "mod": "xivwayfinder",
      "id": "way-arrival-events",
      "title": "Arrival and Change Events",
      "pitch": "Tell other plugins when a target is reached or changes, so Ghostty, XivMcp or a macro plugin can react without polling GetState.",
      "how": "Two message gates next to the existing four in IpcContract.cs: XivWayfinder.v1.Arrived (label, territory) and XivWayfinder.v1.StateChanged (the same JSON as WayfinderIpc.StateJson), sent with ICallGateProvider.SendMessage from Plugin.Update when the arrival timer fires or the guide kind or target changes. docs/IPC.md documents them, and the existing host test that checks every contract name is in the doc covers them.",
      "risks": "StateChanged must be debounced or it fires every frame the distance changes. Subscribers run on the framework thread and a slow one could stall it; the docs must say to return quickly.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 30
    },
    {
      "mod": "almanac",
      "id": "almanac-thread-export",
      "title": "Export a Thread for a Bug Report",
      "pitch": "Save a thread as Markdown with its tool calls, model and settings, after a review step that blanks out names and tokens.",
      "how": "AlmanacStore.Messages(threadId) already returns every message with tool calls and tool results. ChatSession.Render is the model for turning them into lines. Add a header with Engine.Version, engine.ModelLabel, ToolMode and ToolProfile, run a redaction pass seeded from get_player's result and the settings' URLs and tokens, and show the result in a window with per-line exclusion before writing it next to the plugin config directory.",
      "risks": "Redaction by string match will miss things, such as a friend's name in a chat log a tool returned; the review step is the real safeguard and the README's warning about unreviewed transcripts still applies. The export must never include LeaderboardToken, AlmanacToken or XivMcpToken.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 3,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 31
    },
    {
      "mod": "xivpiano",
      "id": "piano-adopt-shared",
      "title": "Make a Shared Station Your Own",
      "pitch": "Turn a station someone shared with you into your own copy, so your thumbs teach it and you can edit its seeds.",
      "how": "Station already carries IsShared from user.getStationList. PandoraClient.TransformSharedStationAsync calls station.transformSharedStation with the stationToken, as pianobar does, and Radio replaces the station in Radio.Stations with the answer. MainWindow shows a small 'shared' tag and the button.",
      "risks": "Few people use shared stations any more, so it is small value. The transformed station gets a new token, so LastStationId and any piano-duty-stations rules pointing at the old one need updating.",
      "feasibility": "ready",
      "effort": "hours",
      "topPick": false,
      "value": 3,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 32
    },
    {
      "mod": "ghostty",
      "id": "stage-directions-apc",
      "title": "Stage Directions in the Byte Stream",
      "pitch": "A private `ESC _ ffxiv;` escape lets your scripts and TUIs cue real lights, bell rings, poses and camera moves on the same frame as the text they print.",
      "how": "Profiles with `stage = true` set GHOSTTY_TERMINAL_OPT_UNKNOWN_SEQUENCE and a 4 KiB OPT_UNKNOWN_MAX_BYTES; libghostty-vt then hands over every APC it doesn't handle itself (kitty `G` and the glyph protocol stay internal), and the callback keeps only `ffxiv;v=1;` payloads. A new Lua hook, stage.cue(id, verbs), maps a whitelist onto what already exists: app/lights.nelua colour and intensity, bell.lua presets, world.place tint and backlight, anim_play, and showcase-style SetCameraState shots. While DATA_MODE reports synchronized output (2026), cues queue and fire when it clears, so a frame and its effect land together. An `ffxiv;q` probe is answered through the session's WRITE_PTY path so scripts can detect support, and a tiny `stage` shell helper ships beside ghostty-agent. Most of the APC-driven ideas in this batch stand on this plumbing.",
      "risks": "Output is untrusted: an ssh session to a hostile box, or a log you cat, could flash lights or yank the camera, so it's off per profile by default, with a verb whitelist and a rate limit. Cues arriving in reattach replay are dropped, the way replayed bells already are. Camera and pose verbs are refused in combat, duties, cutscenes and GPose, and there will never be movement, targeting, chat or command verbs. tmux drops unknown APC unless allow-passthrough is on and the sequence is wrapped.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 33
    },
    {
      "mod": "ghostty",
      "id": "aetheryte-ssh-network",
      "title": "Aetheryte Network for SSH",
      "pitch": "Your ~/.ssh/config becomes a teleport menu, and connecting casts a client-side teleport that lands a shell beside you.",
      "how": "New Nelua primitive ghostty.run(argv, cb) capture session plus ghostty.command(text). Agent-host helper prints TSV from `ssh -G`, `ssh-keygen -F` and a TCP connect time; tmux shards via ControlMaster only on expand. Lua menu in core/ui.nelua table widget; anim_play cast timeline, derez, open `ssh -t host` and pin as pet. No shim changes.",
      "risks": "Can't hijack the real Teleport keybind. Probes can trip 2FA, fail2ban or slow DNS, so TCP connect only. Cast timeline must not lock movement. Real teleport VFX is research.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 34
    },
    {
      "mod": "ghostty",
      "id": "bisect-heaven-on-high",
      "title": "Git Bisect, Heaven-on-High",
      "pitch": "`git bisect` becomes a deep-dungeon descent, one floor per step, ending in a boss room where the first bad commit is waiting.",
      "how": "A precmd hook, plus a `git` wrapper for run mode, checks for .git/BISECT_LOG. When it's there, the hook takes bisect_nr and bisect_steps from `git rev-list --bisect-vars` over the bad and good refs, reads the last verdict from `git bisect log`, and spots the `# first bad commit:` line that log gains when the hunt ends. It prints `ffxiv;bisect;floor=..;left=..;verdict=..` as an APC, caught through OPT_UNKNOWN_SEQUENCE (the Stage Directions plumbing). A new lua/bisect.lua maps good, bad and skip to cairn, trap and pomander effects built from bell.lua rings and the worldview glitch and derez, and on the boss floor pins a panel running `git show --stat` beside the pet.",
      "risks": "It's a theme on top of real work. The precmd hook never fires inside `git bisect run`, so run mode depends on the wrapper. Floor estimates wobble with skips and merges. Deep Dungeon art is limited to game icons through ITextureProvider.GetFromGameIcon and colour. APC through tmux needs passthrough.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 35
    },
    {
      "mod": "ghostty",
      "id": "cast-bar-commands",
      "title": "Commands Cast Like Spells",
      "pitch": "Long-running commands get an FFXIV cast bar and casting pose, and the exit code plays out as a spell effect.",
      "how": "Agent parses OSC 133 C/D in the PTY stream and sends CMD_START/CMD_END frames; OSC 9;4 and 777 via libghostty callbacks as fallback. New lua/cast.lua keeps EWMA durations, a minimum threshold, and picks timelines played through anim_play. Cast bar and particles drawn with ImGui above the projected player. Exit 130 = interrupt. Chime needs a UIGlobals.PlaySoundEffect forward; VFX optional.",
      "risks": "Needs shell integration on every host, tmux passthrough, and remote hooks over ssh. animation.lua's pose guard must yield during casts. Cast timelines vary by job and weapon state. Never touch ActionManager or the real _CastBar.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 36
    },
    {
      "mod": "ghostty",
      "id": "clipboard-crystals",
      "title": "Clipboard Crystals",
      "pitch": "Every OSC 52 copy buds off the panel as an aether crystal that orbits you, and a remote program can read your clipboard only while you hold one out.",
      "how": "Register GHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITE: each GhosttyClipboardWrite carries MIME contents (OSC 52, OSC 1337 Copy or OSC 5522), the program name, granted and can_remember, and is answered inside the callback. Store the crystal (capped with OPT_CLIPBOARD_WRITE_MAX_BYTES) and mirror the chosen one to the desktop with the agent's existing CLIP_SET. OPT_CLIPBOARD_READ must also be answered synchronously, which the agent's round-trip CLIP_GET can't do but a crystal already in memory can: reply SUCCESS with the crystal you're holding out, otherwise DENIED plus a local toast. Dropping a crystal runs ghostty_paste_is_safe (bound today but unused), then ghostty_terminal_paste, so bracketed paste (2004) and kitty paste events (5522) are honoured. Crystals ride the pet springs in lua/world.lua; image/png writes wear a MIME badge until Kitty Graphics Tapestries brings texture upload.",
      "risks": "OSC 52 read is a known way to steal a clipboard, so deny is the default and any remembered grant must be visible and revocable. Many apps never retry a denied read. Crystals hold secrets: memory only, cleared on logout, labels hidden in streamer mode. tmux needs `set-clipboard on`.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 37
    },
    {
      "mod": "ghostty",
      "id": "drive-in-cinema",
      "title": "Drive-In Cinema",
      "pitch": "A cinema-sized curved panel on your island or house wall, lit by the game's sunset and rain, optionally broadcast read-only to opted-in friends.",
      "how": "Solo works now: /term pin here plus lower pixels_per_yalm and a bigger curve radius; rain streaks from ghostty.env(); per-zone derez opt-out in worldview.nelua. Shared viewing (weeks, stretch): agent broadcast mode that streams output only and rejects DATA/OPEN/CLOSE, through a relay with per-invite keys, anchored by house ward/plot/room.",
      "risks": "Truecolor text video repaints every cell and eats FPS; no kitty/sixel on the cell path. No depth, audio stays on the agent host. Agent token grants a shell today, so broadcast must be a separate read-only relay.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 38
    },
    {
      "mod": "ghostty",
      "id": "echo-replays",
      "title": "Echo Replays",
      "pitch": "Replay old terminal sessions as ghost holograms in the exact spot they happened, framed like the game's Echo visions.",
      "how": "Agent records asciicast-style timestamped output and resizes plus territory and coordinates; REC_LIST/REC_OPEN/REC_CHUNK frames. Replay transport feeds Session:feed on a clock, reusing TermView and rendering. Keyframes every ~5s via libghostty formatter for seeking. Flash, rez-in and ghost tint reuse existing effects.",
      "risks": "Recordings keep every token and hostname printed: retention limits, per-profile opt-out, playback redaction. Disk growth. Keyframes may not restore every mode exactly.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 39
    },
    {
      "mod": "ghostty",
      "id": "emoji-pyrotechnics",
      "title": "Emoji Pyrotechnics",
      "pitch": "Sparkle, party-popper and fire emoji printed by your tools burst out of their exact cell as particle effects that spill into the room.",
      "how": "After Session:feed, walk the render state with ghostty_render_state_row_iterator_next_dirty and read GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_GRAPHEMES_UTF8 per cell, matching a table in a new lua/pyro.lua that maps a grapheme to an emitter preset. Grapheme clustering (mode 2027) keeps ZWJ and VS16 sequences in one cell, so a flag or a skin-toned emoji is a single emitter. Each hit pins its row with a capped ghostty_terminal_grid_ref_track so redraws and scrolling don't refire it, and bursts during reattach replay are skipped the way core/bell.nelua skips replayed bells. Cell rects map to world space through world_transform_vertices; particles are quads through the depth-pass callbacks, landing at one Raycast per burst.",
      "risks": "Dalamud's 16-bit ImWchar can't draw emoji outside the BMP, so most source cells still show tofu and the particles are shapes, not glyphs. Emoji-heavy output such as a gitmoji log needs a per-second budget. TUIs that clear and redraw make old emoji look new. Needs a photosensitivity switch, and emitters stay off in duties.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 40
    },
    {
      "mod": "ghostty",
      "id": "glyphfall",
      "title": "Glyphfall",
      "pitch": "Lines that scroll off a world panel fall out of it as real letters that splash in rain, drift in snow and pile up at your feet until you clear the screen.",
      "how": "The core keeps a tracked grid ref on the top row of the active area; after new output, that ref's SCREEN position against DATA_SCROLLBACK_ROWS says how many rows just scrolled off, which keeps working once the 10,000-line scrollback is full and the row count stops growing. Each new history row is read back with ghostty_terminal_grid_ref at a HISTORY point plus ghostty_grid_ref_graphemes and ghostty_grid_ref_style, and every non-blank glyph spawns a particle at its cell's spot on the panel's top edge. One Raycast per batch finds the ground, a cheap integrator moves the letters, and they're drawn with PushWorldFont AddText. A new lua/glyphfall.lua turns the ghostty.env() weather id into behaviour: rain splashes and fades, snow drifts and piles, wind and sand push sideways. An empty viewport right after output triggers the sweep.",
      "risks": "Fast output means thousands of letters a second, so it needs a hard particle cap, sampling of every Nth row and whole-line strips as a fallback. ImGui letters ignore depth unless routed through the depth pass, so piles show through walls. Alt-screen TUIs never scroll into history, so htop never snows. Weather ids need a hand-made table, and house interiors have no weather. It's decoration, not a log.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 41
    },
    {
      "mod": "ghostty",
      "id": "grep-the-world",
      "title": "/grep the World",
      "pitch": "One scrollback search across every terminal you own, with hit counts floating over panels and 'next' flying the camera to the match.",
      "how": "libghostty-vt ships vt/search.h with incremental feed/tick across screens and scrollback. One GhosttySearch per Session, a few ticks per frame. Highlight as selection, scroll via scroll_delta, badges projected in draw_world, counts in the Umbra popup for minimized tabs. Camera reuses fly-in easing; prompt stepping via SEMANTIC_PROMPT.",
      "risks": "Sleeping panels only retain the 256 KiB replay ring; search agent-side or say so plainly. Byte-exact matching, no regex. Prompt stepping needs shell integration.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 42
    },
    {
      "mod": "ghostty",
      "id": "saucer-arcade-cabinets",
      "title": "Saucer Arcade Cabinets",
      "pitch": "Real terminal games run in glowing arcade cabinets along your house wall or beside the Gold Saucer's own machines, with a high-score marquee only you can see.",
      "how": "Each cabinet is a world panel pinned with `/term pin here` using a profile whose command is the game (crawl, nethack, bastet, tint, moon-buggy, ninvaders). A Lua `cabinet` place() shape tilts the panel back, adds a marquee strip and turns the backlight and light_intensity up. Double-click fly-in already exists. While a cabinet has focus, a keymap mode maps GamepadPressed buttons to arrows and letters through the key encoder with setopt_from_terminal, so DECCKM and kitty flags stay right. The core reads DATA_MODE for synchronized output (2026) and skips render_state_update between begin and end, so fast redraws never tear. The marquee flourish comes from a score-file watcher on the host that writes an `ffxiv;score` APC.",
      "risks": "Dalamud can read gamepad state but can't reliably swallow it, so the stick may still walk your character; keyboard or unused buttons are safer. In the crowded Gold Saucer panels without a working depth test cover other players in your view. 80x24 roguelikes need a readable pixels_per_yalm at fly-in size. Scores are local and easy to fake, which is fine because nothing is shared. It never touches MGP or the real minigames.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 43
    },
    {
      "mod": "ghostty",
      "id": "sit-down-to-work",
      "title": "Sit Down to Work",
      "pitch": "Sit in any chair and your panels swing into a seated three-monitor arc while the camera settles into first person.",
      "how": "Detect chair sit from ghostty.anim_state() Mode/ModeParam (or forward EmoteController id). Lua 'desk' layout computes arc slots from seated position and heading and springs anchors there. Camera reuses the double-click Get/SetCameraState path, written once and restored once. Model M joins when it lands.",
      "risks": "Tablet/typing pose overrides may knock you out of the chair, so disable them while seated. First-person near-plane clipping. Triggers in inns and public chairs unless limited to housing.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 44
    },
    {
      "mod": "xivmcp",
      "id": "mcp-agent-board-duty-list",
      "title": "Agent Progress as Tracked Quests",
      "pitch": "The agent board's running entries appear under the Duty List next to custom objectives, with a progress line and a quest toast when one finishes.",
      "how": "ObjectiveOverlay already draws native-styled rows under DutyListAnchor. A second section reads AgentBoard (subscribing to its change event as the IPC Changed gate does) and draws each running post as title plus status line, with the Progress value as a thin bar in the Duty List's colours. Done and Failed transitions call IToastGui.ShowQuest as ObjectiveTracker does. A Settings toggle beside 'Custom objectives' chooses board, objectives or both.",
      "risks": "The overlay itself has never been seen in game (OBJECTIVES.md checklist), so this inherits every placement question. Five busy agents crowd the Duty List; it needs a cap and collapse. Agent names and status text are client-supplied and must be length-capped and cleaned before drawing.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 45
    },
    {
      "mod": "xivpiano",
      "id": "piano-duty-stations",
      "title": "A Station for Every Place",
      "pitch": "Tie a station to a zone, a kind of duty or your home, so the radio changes with where you are and goes back when you leave.",
      "how": "Configuration gains a list of rules (territory id, or a content type from the ContentFinderCondition sheet, or 'housing') to a station Id, like LastStationId. Plugin subscribes to IClientState.TerritoryChanged and IDutyState.DutyStarted, finds the first matching rule, and calls Radio.PlayStationAsync through Plugin.Run with the matching entry of Radio.Stations. The station before the rule is remembered and restored when no rule matches. MainWindow gets a 'Stations for places' section with a 'this zone' button that fills in IClientState.TerritoryType. Zone names come from the TerritoryType sheet through IDataManager.",
      "risks": "Switching stations throws away the current song and fetches a fresh playlist, so rapid zoning (a housing ward tour) would burn through songs; switches need a short settle time. A rule pointing at a station you deleted must fail quietly. Pandora limits how many songs you may skip an hour on some accounts, and changing station is not a skip, but it should be checked.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 46
    },
    {
      "mod": "ghostty",
      "id": "aetheryte-uplink",
      "title": "Aetheryte Uplink",
      "pitch": "Aetherytes become status beacons for your hosts, and teleporting dissolves your terminals into aether that rebuilds on arrival.",
      "how": "Shim adds FindNearest(Aetheryte) from IObjectTable. Lua reuses place_pet orbit math; worldview.nelua draws rings and halo. Host data from the shared probe/EVENT feed. Shim forwards Casting and BetweenAreas; worldview runs combat derez in reverse with a screen-space stream during loading and a line-reveal on arrival.",
      "risks": "Halo is faked, not a real crystal tint; rings draw through geometry. Teleport detection is heuristic (Return, duty transitions). Needs a hide-hostnames option on stream.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 47
    },
    {
      "mod": "ghostty",
      "id": "apps-follow-the-sun",
      "title": "Apps That Follow the Sun",
      "pitch": "The terminal tells your apps it's light mode at Eorzean dawn and dark mode at dusk, so their themes change as the sun comes up over Thanalan.",
      "how": "A Lua hook scheme.decide(env) reads ghostty.env() (day seconds, rain, weather id) plus an indoors flag the shim would add from TerritoryType, and returns light or dark with hysteresis. Sessions register GHOSTTY_TERMINAL_OPT_COLOR_SCHEME so `CSI ? 996 n` queries get the current answer. When the answer flips, every session whose DATA_MODE shows 2031 gets an unsolicited report from ghostty_color_scheme_report_encode written to the PTY. OPT_COLOR_BACKGROUND, FOREGROUND and PALETTE switch to dawn and dusk sets built with ghostty_color_palette_generate and clamped with ghostty_color_contrast, so tools that only query OSC 10/11 at startup get the right colours next launch. Sleeping panels get the latest report on wake.",
      "risks": "Mode 2031 support is uneven, and many tools read OSC 11 once and never again. A mid-session flip can be jarring, so it's per profile with a manual override and flips at most once per Eorzean hour (about three real minutes). Reports only go to apps that enabled 2031, never into a bare prompt. Soul Crystal Themes recolours the terminal; this tells the apps.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 48
    },
    {
      "mod": "ghostty",
      "id": "emote-window-manager",
      "title": "Emotes as a Window Manager",
      "pitch": "/beckon calls a panel over, /point focuses one, /shoo minimizes it, and bound emotes run commands after confirmation.",
      "how": "anim_state() already returns TimelineIds[0]; lua/emotes.lua maps emote timeline ids to actions on rising edge. Point picks the smallest angle off facing. Bound commands go through sendtext.nelua. Sigil is a projected ImGui ellipse. EmoteController.EmoteId forward as backup.",
      "risks": "Pose guard eats emotes within 0.1s, so whitelist them. Confirmation is mandatory and binds default read-only. Slower than hotkeys; mostly for fun.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 49
    },
    {
      "mod": "xivarcade",
      "id": "arcade-save-timeline",
      "title": "Save Timeline",
      "pitch": "A history strip for each game showing every pre-launch backup and synced version, with one button to go back.",
      "how": "backup_saves already keeps the last 10 copies under backups/<game>/<time>/, and Syncthing keeps .stversions in the synced tree. A new helper verb 'history <game>' lists both into state.json, and 'restore <game> <stamp>' runs settle() first, then backup_saves on the current files, then copies through the same never-lose rules as copy_if_newer, forced for this one explicit choice. ArcadeWindow's details strip gets a History button next to Play.",
      "risks": "Restoring while another machine is playing just creates a conflict, so it must refuse while sync says Syncing or the game is running. Standalone memory cards hold many games at once, so restoring one card rolls back every game on it; the window has to say so. Staggered versioning names are Syncthing's own format and may change.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 50
    },
    {
      "mod": "xivdesktop",
      "id": "desk-duty-pop-stow",
      "title": "Stow the Desk When the Duty Pops",
      "pitch": "When you enter a duty or combat starts, hide every window panel, and put them back exactly as they were when it ends.",
      "how": "Plugin.cs already receives ICondition (AskService uses it). A DutyGuard in SessionService watches BoundByDuty and InCombat with a short settle delay, remembers WorkspaceModel's state, and hides everything through the same Plan and window.hide path workspaces use; the exit restores it. Per-condition toggles in Settings, General, plus an exception list for apps the player wants kept, such as voice chat.",
      "risks": "Hiding is presentation only: a video keeps playing and audio keeps going. If a panel owns the keyboard when a pull starts, focus must return to the game first or it is dangerous. Condition flags flicker around cutscenes and zoning, so a naive watcher will thrash.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 51
    },
    {
      "mod": "xivdesktop",
      "id": "desk-gearset-workspaces",
      "title": "Change Job, Change Desk",
      "pitch": "Link a gearset or job to a workspace, so switching to a crafter brings the crafting spreadsheet and switching to a tank brings the raid notes.",
      "how": "Poll the local player's ClassJob once per frame in the IFramework.Update tick KeybindService already runs, or on a class-change event where Dalamud offers one. A Configuration map from ClassJob row id to workspace number, applied through SessionService's existing workspace switch and WorkspaceModel.Plan. Only the player's own job is read.",
      "risks": "Fast gear swaps must be debounced. Switching workspace hides panels; a hidden video keeps playing sound, so this can surprise. Must never fire in a duty unless enabled separately.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 52
    },
    {
      "mod": "xivmcp",
      "id": "mcp-pull-safe-approvals",
      "title": "Approvals That Wait for the Pull to End",
      "pitch": "An agent's gated call that arrives mid-fight becomes a ticket instead of a prompt, and the prompt appears once you are out of combat.",
      "how": "ConfirmationService.ApproveToolCallAsync reads ICondition first: while InCombat (or BoundByDuty with the duty started, the flags get_conditions and get_duty_state already read) it does not open ConfirmWindow but files the call through the same path request_action uses into ApprovalQueue/TicketStore, and the tools/call answers with the ticket id so the client can follow the resume contract in APPROVALS.md. ApprovalWiring's 1 Hz tick raises the toast when the condition clears. A setting under Advanced in MainWindow.Settings.cs turns it off.",
      "risks": "Clients that do not implement the ticket resume contract see a call that never completes, so the answer must say plainly what happened. The 20-second prompt timeout and the call timeout both need rethinking for this path. Condition flags are among the ClientStructs members not yet observed in game. With approval switched off it changes nothing, which is correct but should be said in the setting's text.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 53
    },
    {
      "mod": "xivpiano",
      "id": "piano-station-seeds",
      "title": "Add Variety to a Station",
      "pitch": "See what a station is built from, add an artist or song to it, and take back a thumb you regret, without leaving the game.",
      "how": "PandoraClient gains GetStationAsync (station.getStation with includeExtendedAttributes, returning seeds and feedback), AddMusicAsync (station.addMusic with a musicToken from SearchAsync), DeleteMusicAsync (station.deleteMusic with the seedId) and DeleteFeedbackAsync (station.deleteFeedback with the feedbackId), the calls pianobar makes in src/libpiano/request.c. MainWindow gets a 'Station details' popup reusing the search box from DrawNewStation. Host tests go into PandoraTests against the fake Pandora.",
      "risks": "More Pandora calls means more of an unofficial API to keep working. A station must keep at least one seed, and Pandora's answer to removing the last must read clearly. Shared stations cannot be edited until they are made your own (piano-adopt-shared).",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "verification_pending",
      "evidence": {
        "state": "in progress; live acceptance pending",
        "url": "https://github.com/Spaceghost/xivpiano-dalamud/blob/master/docs/VOTE-IMPLEMENTATION.md"
      },
      "rank": 54
    },
    {
      "mod": "xivwayfinder",
      "id": "way-waypoint-queue",
      "title": "A Queue of Stops",
      "pitch": "Line up several targets and walk them in order: each one is dropped as you reach it and the bead turns to the next.",
      "how": "Plugin.explicitTarget becomes the head of an immutable list swapped atomically (IPC callers are on other threads). The existing ArrivalTimer and ClearOnArrival logic pop the head instead of clearing. New verbs in Commands.cs: add, next, skip, list. A new IPC function XivWayfinder.v1.AddTarget sits beside SetTarget in IpcContract.cs and docs/IPC.md; GetState gains a 'queue' count, which is additive and keeps version 1.",
      "risks": "The order is yours, not an optimised route; people will ask for 'shortest order', which is a travelling-salesman problem across zones. Queue and single-target semantics must stay obvious: does SetTarget replace the whole queue? It should, and the docs must say so.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 55
    },
    {
      "mod": "almanac",
      "id": "almanac-engine-knowledge-bridge",
      "title": "The Engine's Knowledge in Game",
      "pitch": "When the plugin already uses an Almanac gateway, also offer the engine's read-only tools, such as kb_search and model_residency, in the in-game chat.",
      "how": "ToolHub already merges several sources, first-lister wins. When AlmanacSettings.Engine == EngineAlmanac, create a second McpToolSource over McpHttpClient pointed at the engine's MCP port (41880) with AlmanacToken and id \"almanac\", and add it after XivMcp in Engine.NewLoop. Filter to tools whose safety is read, since the engine's change tools rely on MCP elicitation or confirm tokens the chat window does not implement.",
      "risks": "The engine's gateway and MCP server are separate listeners with the same bearer token; a player who exposed one but not the other gets a confusing half-connection. Tool names may collide with XivMcp's, so the engine's need a prefix. Engine tools that run commands on remote hosts should stay out of the in-game list even if marked read.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 56
    },
    {
      "mod": "ghostty",
      "id": "airship-departure-board",
      "title": "Airship Departure Board",
      "pitch": "CI/CD pipelines show as airship departures on a split-flap board at the landing or the FC workshop voyage console.",
      "how": "A world-pinned panel at per-territory coordinates running a `gh run list --json` TUI loop; flip transition per cell in core/render.nelua. Workshop console found by EventObj DataId via a small GetObject extension and pinned like a target anchor.",
      "risks": "Thin novelty over a pinned dashboard. Repo names leak in public spots and on stream, so add redaction. Hardcoded coordinates break on zone changes.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 57
    },
    {
      "mod": "ghostty",
      "id": "credits-roll",
      "title": "Duty Complete Credits Roll",
      "pitch": "Clearing a duty rolls Final Fantasy-style credits across your pet terminals, written from your session.",
      "how": "Shim forwards IDutyState and IPartyList ClassJob ids; icons from ITextureProvider. lua/credits.lua builds text from Enter counts and CMD_END durations and feeds generated VT into a local session. Orbit via SetCameraState spring, gated on WatchingCutscene.",
      "risks": "Must not cover the loot window or exit prompt. Default alias must not leak hostname. Duration stats need OSC 133.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 58
    },
    {
      "mod": "ghostty",
      "id": "eorzea-cron",
      "title": "Eorzea Cron",
      "pitch": "A crontab on Eorzea time and weather that rings your pet terminal for timed nodes and weather spawns.",
      "how": "Host-side: ET = unix*3600/175, weather forecast from WeatherRate tables in a Lumina/Teamcraft dump, run by a systemd user timer. Notification writes BEL into the pet session; bell already bound. Optional amber pre-pulse via EVENT frame and a tint term in lighting().",
      "risks": "WeatherRate tables change with patches. Island Sanctuary and Occult Crescent don't follow simple rules. GatherBuddy already has alarms; the draw is shell scripting.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 59
    },
    {
      "mod": "ghostty",
      "id": "git-status-buffs",
      "title": "Git Status as Buffs and Debuffs",
      "pitch": "Each pet terminal gets a mob-style nameplate with its branch and a buff row for repo state.",
      "how": "precmd hook runs `git status --porcelain=v2 --branch` with a timeout and emits a private OSC user var. Wire UNKNOWN_SEQUENCE in session.nelua to Lua on_uservar. worldview projects the nameplate above each pet via a world.nameplate(id) hook; click uses /term type.",
      "risks": "Slow git in big repos adds prompt lag. Check whether OSC 1337 SetUserVar reaches the callback; else use an unused OSC number. Overlapping nameplates, stale state while asleep.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 60
    },
    {
      "mod": "ghostty",
      "id": "homecoming-boot-sequence",
      "title": "Homecoming Boot Sequence",
      "pitch": "Arriving home boots your installation screen by screen and marks what printed while you were away.",
      "how": "Zone-gated anchors and agent replay already exist. Add on_zone_changed in Lua with staggered boot timers, per-session rez in worldview.nelua, an unread row mark in render.nelua set on sleep, and fold-out to one pet when no anchor matches. Per-house layouts need the housing location hook.",
      "risks": "Amber marking fails for tmux and full-screen apps. Mostly presentation over existing persistence. Staggered boot needs a skip.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 61
    },
    {
      "mod": "ghostty",
      "id": "stream-redaction",
      "title": "Stream Redaction Mode",
      "pitch": "Streamer mode seals password prompts and secret-looking text on every panel before it's drawn, with an optional Eorzean-script skin for looks.",
      "how": "Agent checks tcgetattr after reads and sends SECRET_INPUT when ECHO is off with ICANON. lua/redact.lua regexes dirty rows (ghp_, AKIA, JWT, IPs, *_TOKEN=, key headers); render.nelua draws crystal quads over returned ranges on every surface including the first frame after wake. Cipher skin is a second font handle. Public flag persisted with layout.",
      "risks": "ECHO signal fails inside tmux and ssh, leaving only regex; a missed pattern leaks. Masking must never lag a frame. No hold-to-peek, since OBS captures it. Fan font licensing.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 62
    },
    {
      "mod": "ghostty",
      "id": "terminal-perf-meter",
      "title": "Parse Your Terminals",
      "pitch": "A /term perf meter colors each terminal's frame cost like a DPS parse, and a governor tightens budgets inside duties.",
      "how": "QueryPerformanceCounter around draw_terminal and world_transform_vertices, vertex deltas, byte counters per session. Governor caches flat panel geometry and on throttled frames skips render_state_update and render_termview, re-running only the transform. Shim forwards BoundByDuty, IsGPosing and local chat print.",
      "risks": "Stale geometry cache after resize or font change draws garbage. Combat derez already cuts much of the duty cost. Chat notice should be opt-in.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 63
    },
    {
      "mod": "xivarcade",
      "id": "arcade-conflict-picker",
      "title": "Pick the Winning Save",
      "pitch": "When two machines changed the same save, show both side by side and let you choose, instead of renaming files by hand.",
      "how": "resolve_conflicts already writes the conflicts table (live, kept_as, both mtimes) and names the loser 'name (older save, device ..., date).srm'. Expose unresolved rows through ArcadeSync.Conflicts in state.json with sizes and device names, add a 'swap <conflict id>' helper verb that exchanges the two names after a backup_saves call, and draw the pair in ArcadeWindow under the sync status.",
      "risks": "Newer is not always better, which is the point, but players can still choose wrong; the swap must be reversible and itself recorded. Modification times differ across machines with bad clocks. Needs the sync path tested between two real machines, which has not happened yet.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 64
    },
    {
      "mod": "xivarcade",
      "id": "arcade-crt-glass",
      "title": "CRT Glass and Bezels",
      "pitch": "Per-console scanline shaders and a cabinet bezel on the panel, chosen once and kept out of your own RetroArch settings.",
      "how": "system_cfg_text already appends per-console overrides at launch without touching retroarch.cfg. Add video_shader_enable and a video_shader path, plus input_overlay for a bezel, from a per-console choice in config.json set by '/arcade look <console> <preset|off>'. The helper only points at shader presets already on disk (RetroArch's own slang shaders), and the window lists what it found.",
      "risks": "Shaders cost GPU time inside the agent's headless compositor on top of FFXIV. Flatpak RetroArch sees only paths passed with --filesystem, so shader folders need adding to launch_argv. Standalone emulators are left out. Bezels shipped with the plugin would need their own licence check, so it starts with none.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 65
    },
    {
      "mod": "xivarcade",
      "id": "arcade-crystal-pilgrimage",
      "title": "Crystal Pilgrimage",
      "pitch": "Mark games finished and the Final Fantasy shelf gains a crystal for each one, a quiet record of working through the series.",
      "how": "The 26-title shelf is already built from ArcadeShelfEntry and ArcadeGame.Ff keys. Add a finished flag with a date per game id, written by '/arcade finished <name>' and a button in the details strip, and kept in a small JSON file inside the synced tree so every machine agrees. CoverLayout draws the crystal mark on both picture and drawn covers; hours come from the play journal when present.",
      "risks": "Marking is manual on purpose; reading emulator memory to detect an ending would be fragile and is not wanted. The synced file can conflict like any save and needs merge-by-union rules.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 66
    },
    {
      "mod": "xivdesktop",
      "id": "desk-doctor",
      "title": "/desktop doctor",
      "pitch": "One command that walks every layer between the palette and a visible window, and says which one is broken, with evidence.",
      "how": "A DoctorService in XivDesktop.Plugin runs the checks in order off the framework thread: CatalogService snapshot count and scan time, IpcContract.GhosttyCall/GhosttyPost HasAction, agent.status and focus.get through GhosttyCallBackend, then an opt-in launch of a harmless app and a wait for its request id to resolve in window.list's requests. Each step is a pure record in Core (step, evidence, verdict, fix) so the reasoning is host-testable; the result renders in a small ImGui window and can be copied as a redacted report for the issue template the README asks for.",
      "risks": "It can only report what the IPC tells it; a mapped panel is still not proof the player can see it. The test launch starts a real program on the host, so it must be explicit and use something the player picks. The copied report must strip home paths, tokens and window titles.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 67
    },
    {
      "mod": "xivdesktop",
      "id": "desk-xivmcp-tools",
      "title": "Let XivMcp Drive the Desk",
      "pitch": "Ship the planned list_apps, launch_app, list_windows, window_action, switch_workspace and palette_search tools in XivMcp, on top of the XivDesktop.v1 gates that already exist.",
      "how": "The gates are in src/Shared/IpcContract.cs (ListApps, Launch, Windows, Workspace, Palette, WindowAction) with parse helpers already. XivMcp registers thin tools that call them and return the JSON as is. Launch returns queued, not done, so launch_app should poll Windows for the new panel before reporting success.",
      "risks": "A model that can launch apps can run programs on the host; launch_app needs a per-call confirmation or an allow list, the way the Claude permission dialog works. Tool results must say 'queued' honestly rather than 'opened'. Lives mostly in another repository.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 68
    },
    {
      "mod": "xivlantern",
      "id": "lantern-pull-timer",
      "title": "Channel Pull Timer",
      "pitch": "A countdown anyone in a channel can start, drawn on every member's screen, without touching the game's own countdown.",
      "how": "Channel messages are plain text today (LanternService.SendToChannel calls RoomSend with the string). Add a small structured envelope with a reserved prefix that carries a duration, not a clock time, parsed in XivLantern.Core and tested there. Command.Parse gains 'pull <seconds> [channel]'. The plugin draws an overlay window with no inputs and a toast through the existing INotificationManager at zero. It never sends /countdown or any game command.",
      "risks": "Old clients show the envelope as a raw line, so the prefix must read sensibly as text. Relay latency makes timers drift by a few hundred milliseconds between members. People will ask for it to start the game's countdown; that is a game command and stays out.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 69
    },
    {
      "mod": "xivmcp",
      "id": "mcp-approval-diff-preview",
      "title": "Show Me the Before and After",
      "pitch": "The approval prompt for write_macro, equip_gearset and set_plugin_enabled shows what is there now next to what will be there, not only a sentence.",
      "how": "Tools that already carry an approval sentence gain an optional preview callback: MacroWriteProvider reads the slot through MacroMemory, GearsetProvider reads the target set and get_equipment's current slots. The preview is rendered by ConfirmWindow and the Approvals tab (MainWindow.Approvals.cs) as a two-column table. It also lets McpServer.RenderApprovalSummary stop truncating write_macro at 500 characters, the open point in TODO.md. ToolContractTests gains a check that a preview never replaces the sentence.",
      "risks": "The preview is taken when the prompt opens; the macro could change before approval, so the write must compare against the preview and fail if it moved. Reading macro and gear set memory needs the framework thread before the window draws. A long diff makes the prompt tall.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 70
    },
    {
      "mod": "xivwayfinder",
      "id": "way-breadcrumbs-back",
      "title": "Breadcrumbs Back",
      "pitch": "Remember the way you came and point you back along it: /wayfinder back.",
      "how": "Plugin.Update records your own position into a fixed ring buffer every couple of yalms (allocation-free, covered by AllocationBudgetTests), cleared on zone change. 'back' feeds the reversed crumbs to the existing PathFollow.NextCorner as if they were a vnavmesh path, and DrawTrail shows them. Nothing but your own character is recorded, and nothing is saved to disk.",
      "risks": "Crumbs recorded while flying or swimming do not make a walkable path. Loops and backtracking need simplifying, or the bead leads you round every detour you made. Zone changes break the trail.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 71
    },
    {
      "mod": "almanac",
      "id": "almanac-bench-head-to-head",
      "title": "Head to Head",
      "pitch": "Pick two past benchmark runs and see them side by side, task by task, with the tool calls each model made.",
      "how": "Every run is already in bench_runs with its full ResultJson (AlmanacStore.BenchRuns returns BenchRow). BenchmarkWindow's 'Past runs' header gets checkboxes and a Compare table built from the per-task entries in each result, with columns for tool validity, answer check and time to first token. Only runs with the same SuiteVersion are comparable; others are shown greyed with the reason.",
      "risks": "Results from different context sizes, quantisations or mock versus live mode look comparable and are not, so the table must show those settings at the top. Old result JSON written before a schema change needs tolerant parsing.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 72
    },
    {
      "mod": "almanac",
      "id": "almanac-context-gauge",
      "title": "Context Gauge",
      "pitch": "A small bar in the chat header that shows how full the model's context is, and trims old tool output before it overflows instead of after.",
      "how": "ChatSession.Window cuts to the last 40 messages (HistoryWindow) whatever their size, so one large get_recipe result can fill a small context. ChatResult already carries PromptTokens; read the served context length from Ollama's /api/show or the gateway, store it per model next to model_caps, and replace the message-count window with a token budget that trims old tool output first, as the engine's [agent] context_tokens does. ChatWindow.DrawHeader draws the gauge.",
      "risks": "Not every server reports prompt tokens or its context length, so the gauge needs an honest 'unknown' state and a character-count estimate. Trimming a tool result the model still needs makes it call the tool again or guess. Prompted mode puts the tool list in the system prompt, which counts too.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 73
    },
    {
      "mod": "almanac",
      "id": "almanac-streaming-ask-ipc",
      "title": "Answers Back Over IPC",
      "pitch": "A second IPC version that lets another plugin ask Almanac a question and receive the reply as it streams, not only open the chat window.",
      "how": "Plugin.cs registers Almanac.ApiVersion (1) and Almanac.Ask (Func<string, bool>), which only opens ChatWindow and calls Send. Add ApiVersion 2 with Almanac.AskInThread returning a request id, and an Almanac.Event IPC message carrying the same events ChatSession.OnEvent already handles (TextDeltaEvent, ToolStartedEvent, ToolFinishedEvent, done or error), serialised like the engine's --stream-json lines. Requests run in their own ChatSession so they do not fight the window's thread.",
      "risks": "Two ChatSessions against one local model double the load; requests need a queue and a busy answer. IPC messages are delivered on the caller's thread and a slow subscriber could stall streaming. Any plugin can call it, so it must not accept a system prompt or tool profile from the caller.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 74
    },
    {
      "mod": "almanac",
      "id": "almanac-thread-search",
      "title": "Search Every Thread",
      "pitch": "A search box over every conversation you have had with Almanac, so the answer from last Tuesday is two keystrokes away instead of lost in the sidebar.",
      "how": "Add an FTS5 virtual table over messages.content in AlmanacStore's schema (CREATE ... IF NOT EXISTS beside threads/messages), kept in step by triggers so AppendMessage, ForkThread and DeleteThread need no changes. A new AlmanacStore.Search(query) returns (thread id, seq, snippet). ChatWindow.DrawThreads gets an input above 'New thread'; ChatSession.Open grows a seq to scroll to. Tool results are indexed but ranked below user and assistant lines.",
      "risks": "FTS5 has to be present in the e_sqlite3 the plugin ships and loads inside Dalamud; SqliteNativeTests should assert it the way it asserts the version. The first open on an existing database has to backfill the index, which must not block the framework thread. Prompted-mode tool results are stored as user messages with PromptedTools.ResultPrefix and need filtering.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 75
    },
    {
      "mod": "xivarcade",
      "id": "arcade-custom-shelves",
      "title": "Shelves of Your Own",
      "pitch": "Make your own shelves, such as Backlog, Couch co-op or Comfort games, and put favourites at the top of the wall.",
      "how": "ArcadeWindow already builds its Section list and CoverGrid per console. Add named shelves and favourites, keyed by the stable game_id, in a shelves file in the synced tree; the helper includes them in state.json and the window adds them as sections after the Final Fantasy shelf. Commands: '/arcade shelf <name> add|remove <game>' and a right-click menu on a cover. LaunchBox playlists could be read as starting shelves.",
      "risks": "More shelves make Tab and L1/R1 navigation slower, so empty shelves should hide. game_id is title-based, so renaming a file to a different title drops it from its shelves.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 76
    },
    {
      "mod": "xivarcade",
      "id": "arcade-screenshot-covers",
      "title": "Covers from Your Own Screenshots",
      "pitch": "Take a screenshot in the emulator and use it as that game's cover, a bring-your-own picture with no download involved.",
      "how": "Point RetroArch's screenshot_directory at a per-console folder under the helper's data directory from system_cfg_text. A helper verb 'cover-from-shot <game>' copies the newest screenshot into the art tree as the game's own picture, ranked after the player's covers and before fetched ones in find_boxart. CoverLayout already fits pictures to each console's box aspect without stretching.",
      "risks": "The games folder is never written to, so these must live outside it and not in covers/. Screenshots of wide or odd-resolution games crop badly; a crop preview may be needed. Standalone emulators use their own screenshot folders.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 77
    },
    {
      "mod": "xivarcade",
      "id": "arcade-umbra-widget",
      "title": "Now Playing Widget",
      "pitch": "An Umbra toolbar widget showing the running game and save-sync state, with one click to resume the last game.",
      "how": "Follow the pattern ghostty-dalamud uses for its Umbra.Ghostty widget, without an assembly reference. ArcadeService.State already carries PlayingTitle, Sync.Code and the play history, so the widget reads them through a new XivArcade.v1.Status IPC call and launches through the existing XivArcade.v1.Launch.",
      "risks": "Umbra's widget API changes between versions, and XivArcade must keep working with Umbra missing. The status call must be cheap, because toolbars poll often.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 78
    },
    {
      "mod": "xivmcp",
      "id": "mcp-dresser-armoire-tidy",
      "title": "Dresser and Armoire Tidy",
      "pitch": "A read-only report of glamour items that could move to the Armoire, duplicates across dresser and bags, and sets that could be combined into outfits.",
      "how": "A new tool in the Inventory providers joins the dresser contents GlamourProvider already reads with InventoryReader's containers and cached retainer inventories, then checks each item against the Cabinet sheet (Armoire-eligible) and the MirageStoreSetItem sheet (outfit sets) through IGameDataSource. It returns lists only; nothing is moved.",
      "risks": "Dresser and retainer contents are only loaded after the player has opened them this session, so the answer must say which containers were not seen. Outfit rules around dyes and duplicates have edge cases. Pure planning, which is the point, but some will ask it to do the moving; it must not.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 79
    },
    {
      "mod": "almanac",
      "id": "almanac-tool-call-inspector",
      "title": "Tool Call Inspector",
      "pitch": "Click any grey tool line in the transcript to see the full arguments and result, how long it took, and whether the call passed schema validation.",
      "how": "ChatSession.Shorten cuts tool lines to 120 characters and Render throws the rest away. Keep the StoredMessage seq on tool lines (it is already there), and on click load the full message from AlmanacStore.Messages, pretty-print it with System.Text.Json, and pair each call with its result by ToolCallId. Validation reasons come from Scorer.Validate as AgentLoop already records them in CallRecord. Rerun is only offered for tools in the read tier.",
      "risks": "Tool results can be large, such as a full inventory, and drawing them every frame in ImGui is slow unless the text is cached and clipped. Prompted-mode threads store calls as plain assistant text, so pairing needs the PromptedTools parser. Rerunning an action-tier call must never be possible from here.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 80
    },
    {
      "mod": "xivdesktop",
      "id": "desk-taskbar-icons",
      "title": "Proper Icons on the Umbra Taskbar",
      "pitch": "Show each open window's real app icon in the Umbra Windows widget instead of a letter tile.",
      "how": "WindowsWidget.Item sets a LetterTile colour and letter today. Add an optional icon path to AppSummary and the Windows payload in IpcContract (additive, as the contract requires), filled from CatalogService's resolved PNG path, and let the widget load it through Una.Drawing's image style. Falls back to the letter tile when absent.",
      "risks": "The Umbra layout and the reflection-based popup workaround are themselves unverified in game. Icon textures must be loaded once and shared, not per redraw. Depends on desk-svg-icons for full coverage.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 81
    },
    {
      "mod": "xivwayfinder",
      "id": "way-proving-ground",
      "title": "The Proving Ground",
      "pitch": "A step-by-step window that walks you through the in-game checks in docs/WAYFINDER.md and builds a report to paste into an issue.",
      "how": "A new window beside SettingsWindow, with the eight steps from 'Proving it in game' as data. Steps that can be checked automatically read WayfinderState (guide Walk, distance close to 20, source Flag, source Quest, Navmesh true, reason 'cutscene'); the others are manual ticks. The report joins WayfinderIpc.StatusLine, GloveTexture.Status and Navmesh.Status and goes to the clipboard, never the network.",
      "risks": "An automatic tick proves only what the plugin believes, not what the player saw, so the report must keep the two kinds of evidence apart. The step list must stay in step with the doc.",
      "feasibility": "ready",
      "effort": "days",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 82
    },
    {
      "mod": "ghostty",
      "id": "folding-screen-splits",
      "title": "Folding Screen Splits",
      "pitch": "Splitting a world terminal hinges it into a folding screen of panes you can angle, fold shut and walk around.",
      "how": "Each leaf is its own Session; add group id and leaf index to layout persistence. Lua computes leaf placement from the group anchor with hinge yaw animated by spring(). No new projection math. Hinge drag is a new grip mode calling policy_world_hinge. Folded leaves sleep via sleep_unseen. /term split and fold commands.",
      "risks": "Overlapping leaves need depth-sorted picking. Long-folded panes lose older scrollback on wake. Resizing a leaf must rebalance neighbours.",
      "feasibility": "ready",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 83
    },
    {
      "mod": "xivlantern",
      "id": "lantern-signed-release-notes",
      "title": "News From the Lamplighter",
      "pitch": "Each release posts its changelog entry to the author channel as a signed announcement, so players who opted in hear about fixes in game.",
      "how": "tools/release.sh and tools/changelog.py already produce the player-facing entry from changelog.json. Add a step that signs it offline with the lantern CLI's author sign command (crates/lantern-cli/src/author.rs, which prints an ltannounce line with a growing --seq) and hands that line to the author node's /announce, per docs/AUTHOR.md. The plugin already verifies and shows announcements (LT_EV_ANNOUNCEMENT, AuthorChat).",
      "risks": "AuthorIdentity.cs is still empty, so nothing can be verified until the author does the AUTHOR.md setup. Signing must stay on the author's machine and never move into CI. Too many announcements will get the channel muted.",
      "feasibility": "stretch",
      "effort": "hours",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 84
    },
    {
      "mod": "ghostty",
      "id": "failing-suite-extreme",
      "title": "The Failing Suite (Extreme)",
      "pitch": "Target a striking dummy and your test suite becomes a trial boss: failing tests are its HP, compiler errors pull as adds on an enemy list, and all green plays Duty Complete.",
      "how": "A host wrapper runs the suite and the checker under watchexec, reads JUnit or JSON output (`cargo check --message-format=json`, `tsc --watch`, `ruff --output-format json`) and prints `ESC _ ffxiv;suite;pass=..;fail=..;fixed=..;top=<test> ESC \\` plus `ffxiv;diag;<json>` snapshots, caught through OPT_UNKNOWN_SEQUENCE (session.nelua sets neither option today). A new lua/trial.lua diffs snapshots into damage, new adds and kills, ranks adds with primary errors first, and keeps a local hunt log. The hologram is a world panel on the existing `target` anchor raised above the dummy, running `less +G` on the top failure. Boss bar, enemy list and banner are ImGui in chrome.nelua's glass style; numbers go through a new shim call to IFlyTextGui.AddFlyText, and the nameplate is renamed in INamePlateGui.OnNamePlateUpdate only for a BattleNpc whose BNpcName is a striking dummy. Clicking an add writes `goto file:line` to the wrapper, which calls `nvim --server $NVIM --remote-send`.",
      "risks": "Hitting the dummy puts you in combat, and combat derezzes everything, so this is a boss you watch, not one you fight. AddFlyText may only attach to your own character, so the dummy's numbers might have to be drawn by the plugin. One refactor can spawn 300 adds, so cap and roll them up, and crediting a root-cause fix with its cascade is a heuristic. Giant tracebacks carry paths and secrets. It's gated to dummies outside duties so it never looks like a real combat HUD.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 85
    },
    {
      "mod": "ghostty",
      "id": "your-feet-are-the-mouse",
      "title": "Your Feet Are the Mouse",
      "pitch": "Lay a terminal flat on the floor and any mouse-aware TUI follows your character's footsteps as the pointer, with a jump as the click.",
      "how": "place() already takes pitch, and world_axes and the depth-pass basis tilt with it, so a `floor` pin lays the panel flat at a Raycast ground height. Each frame ghostty.player() x/z is projected into panel pixels through that basis. The core builds a ghostty_mouse_encoder with setopt_from_terminal, so tracking (1000/1002/1003) and format (SGR 1006, SGR-pixels 1016) come from the app, plus OPT_SIZE and OPT_TRACK_LAST_CELL so motion reports only when you cross a cell. The shim adds ConditionFlag.Jumping to GuObject; its rising edge after a short dwell on a tile sends a button-1 press and release. Stepping on or off the board sends ghostty_focus_encode in or out when mode 1004 is set. Only your own character counts, and only in floor mode.",
      "risks": "The plugin has never reported a click to a TUI (termview only encodes the wheel), so this is the mouse encoder's first user. Jumps are noisy, hence the dwell. Floors aren't flat everywhere, so raycast the corners and refuse slopes, and a flat panel may z-fight the floor in the depth test. Other players' positions must never become input.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 86
    },
    {
      "mod": "ghostty",
      "id": "algorave-conductor",
      "title": "Algorave Conductor",
      "pitch": "Live-code music in a terminal and your character conducts at the exact tempo, while the pattern that just played flares on the panel behind you.",
      "how": "TidalCycles and Strudel's desktop setups send every event to SuperDirt as OSC over UDP. A host-side `ffxiv-tidal` reads a mirrored copy of that stream (orbit, cps, cycle, delta, sound) and prints `ffxiv;beat` APCs into a hidden session kept live with run_occluded, caught through OPT_UNKNOWN_SEQUENCE. A new lua/conductor.lua converts cps to BPM and drives ghostty.anim_speed on a pose from animation.lua picked per tempo band, with anim_play flourishes on section changes, all client-side like the existing typing loop. For the flash it finds the row holding `d1 $` on the editor panel with ghostty_search_* and GHOSTTY_SEARCH_DATA_VIEWPORT_MATCHES, then glows that rect in the orbit's colour for the event's delta. Sonic Pi works the same way through its OSC cue log.",
      "risks": "Poses played far off their normal speed look silly, so stick to tempo bands. Tidal doesn't report source spans, so matching an event to its line is a guess; Strudel's events do carry them. The UDP mirror must never add latency to SuperDirt. Animation changes are client-side but stay off in duties. Nobody in the venue sees the code unless they watch your stream.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 87
    },
    {
      "mod": "ghostty",
      "id": "cracktro-login",
      "title": "Cracktro Login",
      "pitch": "Launching the game plays a 1990s cracktro over the title screen: a sine-scrolling greetz list, copper bars, a starfield and a MOD tune from your desk speakers.",
      "how": "The intro is an ordinary terminal program on the agent host: `cracktro` draws truecolor half-block frames (kitty graphics once Kitty Graphics Tapestries lands) and plays the .mod with openmpt123. The core opens it at the title screen, when host.nelua reports not in world and IClientState.IsLoggedIn is false, as a borderless full-screen popup terminal. It holds render_state_update while DATA_MODE shows synchronized output (2026), so each frame presents whole. Greetz come from greetz.txt plus an FC name and friend names cached at your last logout (ClientStructs InfoProxyFriendList; fields to verify). Any key or pad button ends it with the existing derez, once per launch, skippable in settings.",
      "risks": "Full-screen half-blocks at speed are heavy on the per-glyph ImDrawList path. render.nelua doesn't honour mode 2026 today. If the agent isn't reachable before login it must show nothing and never delay the menu. Friends' names on a streamed title screen are a leak, so friend greetz are opt-in. Copper bars need a calm preset, and MOD files carry their own licences.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 88
    },
    {
      "mod": "ghostty",
      "id": "cutscene-director",
      "title": "Cutscene Director",
      "pitch": "Long commands finish like a story beat: letterbox bars, a camera move to the panel, and a Talk-style box reading the result.",
      "how": "Shares the OSC 133 CMD_END frame with cast-bar-commands. Shim forwards BoundByDuty/WatchingCutscene/OccupiedInEvent and IToastGui. lua/director.lua sequences bars, camera (existing fly-in SetCameraState), box and return. Talk box is ImGui with game ULD textures, not the real AddonTalk.",
      "risks": "Unprompted camera moves are jarring; abort on any input or condition change. Needs shell integration. Command names show on stream.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 89
    },
    {
      "mod": "ghostty",
      "id": "deploy-raid-pull",
      "title": "Deploy as a Raid Pull",
      "pitch": "CI jobs show as party frames, and a production deploy needs a charged Limit Break hold before Enter goes through.",
      "how": "Capture session polls `gh run list/view --json` every ~10s to TSV; frames drawn with new ui.* progress widgets. ghostty.input_line(session) reads cursor-row cells with SEMANTIC_CONTENT==INPUT (OSC 133). keymap.lua swallows Enter on guarded patterns until the hold fills, then sends \\r. Victory via anim_play, skipped while BoundByDuty.",
      "risks": "A speed bump, not a safety control: needs OSC 133 and is bypassed from any other terminal. Swallowing Enter needs a visible reason and Esc to cancel. gh rate limits.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 90
    },
    {
      "mod": "ghostty",
      "id": "familiar-tells-ready-check",
      "title": "Ready Checks for Your Agents",
      "pitch": "When an agent needs you mid-duty it waits for the wipe or the kill, and then every pending question arrives at once as a ready check.",
      "how": "Claude Code's Notification and Stop hooks send `ghostty-emit waiting` (the EVENT frame from Your Agents Are a Light Party); other agents can use OSC 9 or 777, which reach GHOSTTY_TERMINAL_OPT_DESKTOP_NOTIFICATION once session.nelua registers it. Out of combat the shim prints the line with IChatGui.Print and a link payload that focuses the session's panel; while InCombat or BoundByDuty is set, lines queue. The Ready Check opens on ICondition.ConditionChange (combat off) or IDutyState.DutyWiped and DutyCompleted, as a local window styled after the game's, never the real /readycheck. Each row renders the session's bottom rows from render state, options are parsed from lines shaped like `1. Yes`, and a click sends only that digit through the key encoder to that session. CLAUDE.md can also tell agents to call an in-game MCP server's duty-state tool before asking and keep working while you're inside.",
      "risks": "One-click approvals between pulls is how you approve an `rm -rf` without reading it, so rows show the full command and destructive patterns can't be answered from the check. Parsing Claude Code's prompt off the screen breaks when its TUI changes. Local lines must never look like a real player's tell, both for clarity and honest screenshots. Nothing is ever sent to other players.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 91
    },
    {
      "mod": "ghostty",
      "id": "feed-the-familiar",
      "title": "Feed It to the Familiar",
      "pitch": "Alt-drag a failing terminal onto your AI companion and it swallows the last command's output as context and starts on the fix.",
      "how": "world.drop(id, pose) already fires when an Alt-drag ends (core/policy.nelua:544); lua/world.lua checks the drop point against panels whose profile has `familiar = true`. The core scans GHOSTTY_ROW_DATA_SEMANTIC_PROMPT upward for the last OSC 133 prompt, takes a grid ref on the output after it, calls ghostty_terminal_select_output and ghostty_terminal_selection_format_alloc (plain, unwrapped, trimmed, capped to the tail). A header adds DATA_TITLE, DATA_PWD from OSC 7, the command from INPUT-semantic cells and the exit code from the agent's CMD_END frame. The block goes through ghostty_paste_encode into the familiar's session as a bracketed paste. Without OSC 133 it falls back to a PLAIN formatter over the viewport. The swallow reuses the present and fly-in tween in worldview.nelua.",
      "risks": "It sends terminal output, env dumps and tokens in stderr included, to a hosted model: show a byte count, redact token-shaped strings, confirm the first time. If the familiar sits in a permission prompt the paste lands in the wrong place, so drops are refused unless its last hook state is idle. The source shell needs OSC 133 through tmux and ssh. Alt-drag already means move, so drop targets need a clear highlight.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 92
    },
    {
      "mod": "ghostty",
      "id": "fishing-for-errors",
      "title": "Fishing for Errors",
      "pitch": "Each new kind of error on your machines becomes a fish in the pond, and when you land a real catch it leaps out as a glowing hologram and goes into your Error Guide.",
      "how": "On the agent host, `xiv-pond` tails journalctl and app logs, collapses lines into signatures by stripping numbers, hex and paths (drain3-style templates), and writes `ESC _ ffxiv;fish;<sig>;<severity>;<count>;<text> ESC \\` into a hidden session kept live with run_occluded. The core captures these through OPT_UNKNOWN_SEQUENCE and queues them. The shim adds a read of ConditionFlag.Fishing and an IChatGui.ChatMessage filter for your own catch message, and only after that message does Lua pop the most severe queued error. Its leap arc starts at a Raycast point ahead of your facing; severity and count set size and colour. The Error Guide is a ghostty.ui tab saved beside world-state.lua; double-click an entry to open its log on a panel.",
      "risks": "It must never signal bites, hook timing or anything else about the real fish, which would be fishing assistance; it reacts only after the game has told you what you caught. Catch messages differ by client language. Bad templates turn one bug into a hundred species. Log lines carry tokens and hostnames, so redact on the host. It's flavour, not monitoring, so keep a real alert alongside it.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 93
    },
    {
      "mod": "ghostty",
      "id": "guest-terminal-scenes",
      "title": "Allagan Terminal Scenes",
      "pitch": "RP hosts and venues run hackable in-world consoles backed by sandboxed scripts that invited players walk up to and use.",
      "how": "World anchor shared in the invite. Agent guest tokens with a forced command, like ssh ForceCommand: ATTACH only to the scene session, which runs in a container or restricted user. Client line-buffers and tags lines with name@world. Restricted session mode ignores OSC 52, notifications and hyperlinks. Theme palette plus a licensed fan font via the font atlas. Venue discovery by opt-in directory keyed on ward/plot is research.",
      "risks": "Guests on a host is the core security problem: sandbox mandatory, rate caps, relay to hide IPs. Content sits outside SE moderation. Tiny audience, and non-plugin players see people typing at thin air.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 94
    },
    {
      "mod": "ghostty",
      "id": "loot-roll-job-results",
      "title": "Loot-Roll Job Results",
      "pitch": "Jobs that finish mid-fight wait for combat to end, then drop at your feet as coffers you triage with Need, Greed or Pass.",
      "how": "`term notify` asks the agent via a control socket to OPEN a detached session; a PROTO_EVENT frame reports completion. Nelua queues while in_combat and releases after ~5s out. Lua 'drop' anchor springs at your feet tinted by status. Need=ATTACH+make_pet, Greed=OPEN same argv, Pass=CLOSE via existing gesture code.",
      "risks": "Combat flag flickers on trains; debounce. Skip bindings while the game's NeedGreed addon is open. Greed re-running a deploy needs confirmation. ssh-launched jobs need the socket forwarded.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 95
    },
    {
      "mod": "ghostty",
      "id": "moogle-mail",
      "title": "Moogle Mail",
      "pitch": "When a background job, ssh session or tmux alert finishes, a delivery moogle only you can see hands you the result as a letter.",
      "how": "Triggers: agent EXIT for detached sessions, OSC 9/777 and bell callbacks, plus an agent-side NOTIFY frame so sleeping sessions don't miss completions. mail.lua queues letters (id, title, last N lines), holds them until out of combat and duty, and persists with layout. Parchment theme in render.nelua; letter opens as a pet via place_pet. v1 moogle: ImGui billboard from a game icon texture on pet springs; optional local IChatGui.Print with a link payload. v2: client-side moogle actor (weeks, research).",
      "risks": "Client-side actors crash on zoning and must never spawn in duties or cutscenes. Sprite draws through walls. Needs shell integration or wrappers. Stderr on screen can leak secrets on stream. Share one pipeline with the visual bell and taskbar.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 96
    },
    {
      "mod": "ghostty",
      "id": "ops-weather",
      "title": "Ops Weather",
      "pitch": "Server health sets your local weather, client-side, so you feel an outage before you read about it.",
      "how": "Agent probe runner sends status over a new EVENT frame. New ops.lua maps states to per-territory weather ids with hysteresis. Shim adds SetWeatherOverride/ClearWeatherOverride beside GetEnvironment using the client-side WeatherManager path Brio and Weatherman use. M.lighting() already reads weather and rain, so tint and backlight follow for free; rain beads drawn in worldview.nelua.",
      "risks": "Not every weather id is valid per territory; houses and duties have none or script their own. Hides real weather from fishers and gatherers, so it needs a toggle and guaranteed restore on unload, zone change and duty. Fights Brio or Weatherman.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 97
    },
    {
      "mod": "ghostty",
      "id": "over-the-shoulder-pets",
      "title": "Over-the-Shoulder Pets",
      "pitch": "Friends who opt in make their orbiting pet terminal visible to you as a live read-only screen.",
      "how": "Read-only guest tokens scoped to one session plus a PAUSE frame on minimize; LIST already reports attached count. A second AgentClient per share with sessions flagged read-only. Invite carries name@world, matched against target/object table. Generalize the pet anchor to any entity_id.",
      "risks": "Screens may show secrets; needs a clear broadcasting indicator. Skip or clear the 256 KiB replay for guests. Pet positions won't line up exactly across clients.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 98
    },
    {
      "mod": "ghostty",
      "id": "shell-tomestones",
      "title": "Tomestones for Dead Shells",
      "pitch": "When a process dies its panel falls and leaves a tomestone on the spot, holding an exact snapshot of its last screen and scrollback.",
      "how": "On PROTO_EXIT, Session:mark_exited calls ghostty_snapshot_encode_alloc and writes a GHOSTSNP file beside world-state.lua with territory, pose, argv, cwd (DATA_PWD from OSC 7), title (DATA_TITLE), runtime and status; the agent already reports signals as 128+N. The panel derezzes into a drawn slab with the epitaph. Opening a stone builds a transport-less, read-only Session: ghostty_snapshot_decoder_new_buf, decode to decoder_ready so the final screen draws at once, then decoder_next brings older scrollback in as you scroll. A sleeping panel is detached and never sees EXIT, and the agent drops exited sessions, so the agent must hold a finished session until a client collects it, or link libghostty-vt itself (tools/build.sh already builds the host library) and snapshot at the moment of death. `/tome` is already an alias of `/term`, so `raise` is just a new subcommand.",
      "risks": "Snapshots keep every secret that was on screen: per-profile opt-out, a retention cap and make_private file permissions. vt.h warns the API will change, so version the files and treat old ones as best effort. A graveyard of every `ls` would be silly, so only long runs or non-zero deaths get a stone.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 99
    },
    {
      "mod": "ghostty",
      "id": "static-war-room",
      "title": "Static War Room",
      "pitch": "Your static or FC gets one read-only strat board pinned at the same arena wall or FC house spot on every member's client.",
      "how": "World anchors are identical across instances; share as a save_state snippet restored when zone matches (FC house needs HousingManager id). Members attach with read-only guest tokens to a separate broadcast agent; one PTY means scroll sync is free. Shim forwards IDutyState start/wipe/complete; only the lead writes pull number and time. Leader ping is an EVENT frame that turns pets and highlights rows.",
      "risks": "Never parse combat events or show timelines, which would make it a trigger tool. Lead's agent dropping kills everyone's board. Discord screen share covers the basics. Needs opacity for sightlines.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 100
    },
    {
      "mod": "ghostty",
      "id": "wardrobe-by-hostname",
      "title": "Wardrobe by Hostname",
      "pitch": "Glamourer IPC dresses you for the machine you're on, so prod looks different from dev.",
      "how": "OSC 7 PWD_CHANGED and title give the host; forward on_host(id, host) to Lua. lua/wardrobe.lua maps globs to Glamourer designs for the focused session with ~1s debounce. Shim adds typed forwards for Glamourer apply/revert with a lock key. Sudo from a prompt regex on the cursor row.",
      "risks": "Plain ssh without OSC 7 or titles gives nothing. Glamourer automation can fight the state; crash mid-apply leaves the outfit on. IPC signatures change, so pin the API version.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 101
    },
    {
      "mod": "xivarcade",
      "id": "arcade-duty-pop-pause",
      "title": "Duty Pop Pause",
      "pitch": "When the Duty Finder pops, your arcade game saves a state and pauses, and it offers to resume when you are back.",
      "how": "Add network_cmd_enable and a fixed network_cmd_port to system_cfg_text in tools/xiv-arcade, so every RetroArch launch accepts local UDP commands. PadCaptureService already watches ICondition for combat and cutscenes; add a DutyPop reason to the PadRelease enum, raised from ConditionFlag.WaitingForDuty or an IAddonLifecycle PostSetup on ContentsFinderConfirm. On it, ArcadeService asks the helper (new 'pause' verb) to send SAVE_STATE then PAUSE_TOGGLE, checked with GET_STATUS first so a paused game is not unpaused. On leaving the duty, the resume offer is a chat link, never automatic.",
      "risks": "It must only watch the pop, never click Commence; that line is absolute. PAUSE_TOGGLE is a toggle, so a lost reply can leave the game running. Standalone PCSX2 and DuckStation have no equivalent command channel, so they get the pad release only. The UDP port is open on localhost to anything on the machine. None of the underlying pad or panel path has been seen in game yet.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 102
    },
    {
      "mod": "xivmcp",
      "id": "mcp-agent-linkshell",
      "title": "A Linkshell for Your Agents",
      "pitch": "A private in-game channel where agents post short messages in their own colour and you reply with a slash command, delivered to them as a resource update.",
      "how": "print_echo already writes [MCP]-tagged lines to the player's own log only. Give each client a stable colour and prefix, and add `/xivmcp say <agent> <text>` in the command handler Plugin.cs registers; messages go to a bounded per-client inbox exposed as ffxiv://inbox with notifications/resources/updated through NotifierProxy, plus a read_inbox tool. The EventStream gains a 'message' kind so get_events clients see it too.",
      "risks": "Echo lines are the player's own log, but they still sit in chat logs that other plugins read and export, so secrets posted by an agent end up there. Clients must subscribe to resources for replies to feel live. A /say typo such as '/say claude' would post in public Say chat, so the command name must be hard to confuse.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 103
    },
    {
      "mod": "xivmcp",
      "id": "mcp-sightseeing-log-objectives",
      "title": "Your Unfinished Sightseeing Log, as Objectives",
      "pitch": "A tool that reads which vistas you have not logged and posts each as a custom objective with its spot, emote, Eorzea time and weather window.",
      "how": "A static provider reads the Adventure sheet through IGameDataSource (so the standalone host can answer the data half) for the Level location, emote and time columns; a live half checks completion through ClientStructs' PlayerState adventure flags via ILiveWorld. The result feeds ObjectiveFactory directly, and the time and weather become the objective's eorzeaTime and weather conditions that ObjectiveEvaluator and find_weather_windows already compute.",
      "risks": "Which Adventure columns carry the weather requirement must be checked against Lumina and may need a small hand-kept table for some entries. The completion flags are unverified in game like every ClientStructs read here. Posting a whole expansion's log at once hits the 200-objective cap, so it works a region at a time.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 104
    },
    {
      "mod": "xivwayfinder",
      "id": "way-find-npc",
      "title": "Where Is the Mender?",
      "pitch": "Point at an NPC by name, /wayfinder find mender, using where the game's own data places them.",
      "how": "Build an index once from the Level sheet rows whose Type is an ENpc, joined to ENpcResident for names and titles ('Mender' is usually a title). Match with ZoneMatch's exact, then prefix, then substring ranking over ZoneMatch.Fold. Among placements pick the nearest in your zone first, like QuestPick.Nearest, else fall back to the aetheryte hint. Results become an Explicit Target with the NPC's name as the label, Y included so vnavmesh gets a proper floor.",
      "risks": "Many NPCs have several Level rows or appear only during certain quest phases, so the game data can place someone who is not there. Generic titles match dozens of NPCs. Building the index costs a noticeable moment on first use and must happen off the framework thread.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 105
    },
    {
      "mod": "xivwayfinder",
      "id": "way-route-codes",
      "title": "Treasure Hunt Routes",
      "pitch": "Build a route of stops with a riddle at each and share it as a pasteable code, so an FC can run a scavenger hunt with nothing but chat.",
      "how": "A route is a small JSON list of (territory, x, z, label, clue, hidden) compressed and base64-encoded with a version prefix; parsing and validation go in Core using WayfinderIpc.Validate and CleanLabel for every stop, so a malicious code can do nothing but point. Builds on way-waypoint-queue. 'Hidden' stops show no bead until the previous one is reached, only the clue in DrawNote. Recording a route is /wayfinder route add at each spot. No network: the code is passed by hand.",
      "risks": "Codes get long for many stops and chat limits message length. Hidden stops can be cheated by reading the code, which is fine for fun and should be said plainly. Clues are player-written text, so they need the same cleaning labels get.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 106
    },
    {
      "mod": "almanac",
      "id": "almanac-clickable-answers",
      "title": "Answers You Can Click",
      "pitch": "Map coordinates, item names and duty names in a reply become small buttons that place the flag or show the details, without asking the model again.",
      "how": "Structured tool results (ToolOutcome.Json, kept in CallRecord) already name the zones, items and coordinates the answer was built from, so matching the reply against those is more reliable than parsing free text. ChatWindow's LineKind.Assistant branch renders spans instead of one TextUnformatted. Clicks call engine.XivMcpToolsAsync() then McpToolSource.CallAsync(\"set_map_flag\" | \"get_item\" | \"find_owned_items\") directly, going through XivMcp's normal tiers.",
      "risks": "Wrapped ImGui text with inline buttons is fiddly and costs frame time on long transcripts. A model that invents coordinates would get a flag in the wrong place, so only values that came out of a tool result become links. Old threads reopened from SQLite have lost the in-memory CallRecords unless tool JSON is re-parsed from the stored messages.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 107
    },
    {
      "mod": "almanac",
      "id": "almanac-spoiler-guard",
      "title": "Spoiler Guard",
      "pitch": "Almanac checks how far you are in the main scenario and keeps its answers about lore, NPCs and later zones behind that line unless you ask it not to.",
      "how": "At the start of a thread, and when the zone changes, call XivMcp's get_quest_status for a short list of main scenario milestones and put the furthest completed one into the system prompt that ChatSession builds each turn, with a rule not to reveal later events. A toggle in settings and a /almanac spoilers on|off subcommand in Plugin.OnCommand. Answers based on search_quests or get_quest for quests beyond that point get a warning line.",
      "risks": "A local model's own training is the main source of spoilers and a prompt rule will not stop all of them; this lowers the risk and must say so. Choosing the milestone list takes knowledge of the story and upkeep every patch. Players on a new character may have finished the story on another.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 108
    },
    {
      "mod": "ghostty",
      "id": "flow-state-limit-break",
      "title": "Flow-State Limit Break",
      "pitch": "Typing fast fills a client-side Limit Break gauge that fires a flourish at your peak.",
      "how": "Extend animation.lua typing energy with a rolling WPM window and three segments; ImGui gauge; payoff via anim_play of a job LB timeline the pose guard allows. Aura needs an actor VFX forward. Finger IK toward the Model M is dropped.",
      "risks": "LB timelines are job-specific, some long and camera-bound. Rewards typing garbage into a shell. Aura VFX is stretch.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 109
    },
    {
      "mod": "ghostty",
      "id": "furniture-anchors",
      "title": "Mount to Furnishing",
      "pitch": "Pin terminals to real housing items, so a painting or bookshelf becomes a screen that moves with the furniture.",
      "how": "Shim forwards HousingManager location (house/ward/plot/room) and IndoorTerritory furniture (item id, transform), as MakePlace and BDTH read. New 'furniture' anchor in lua/world.lua matched by item id plus nearest transform, followed every frame, with per-item offset/size from a hand-made table. Replaces zone check with house-id check, since same-size houses share a territory id.",
      "risks": "Panels show through walls and floors without depth; gate by room or height. Structs change with patches and load async. Identical adjacent items confuse matching. Frame fitting needs manual calibration.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 110
    },
    {
      "mod": "ghostty",
      "id": "legible-at-distance",
      "title": "Legible at 40 Yalms",
      "pitch": "Distance LOD for world panels: full text up close, block minimap rows mid-range, a one-line nameplate far away.",
      "how": "draw_world already has projected corners; divide by rows for on-screen cell height and pick glyphs, run blocks (reuse the merged background-run loop), or nameplate from title, last row or OSC 9;4 progress. Lua owns thresholds with hysteresis. Font mip handles at 20/40/80px chosen in PushWorldFont. MSDF is research.",
      "risks": "No ligatures: per-cell AddText, no shaper. Big font atlases grow fast. LOD pop needs cross-fade.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 111
    },
    {
      "mod": "ghostty",
      "id": "queue-pop-eject",
      "title": "Queue Pop Eject",
      "pitch": "When the Duty Finder pops, whatever terminal owns your screen lets go, and an opt-in hook lets your desk react.",
      "how": "Shim forwards IClientState.CfPop duty id via gu_event. Nelua clears world_full_id and stops capturing keyboard. Lua on_duty_pop picks pet or minimize and sets a countdown title cleared on WaitingForDutyFinder/BoundByDuty. Client-to-agent EVENT frame fork/execs an opt-in hook with only DUTY_ID in env.",
      "risks": "Focus not released means you can't press Commence; test hardest. CfPop may miss some queue types. Never auto-commit, never type into the PTY.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 112
    },
    {
      "mod": "ghostty",
      "id": "soul-crystal-themes",
      "title": "Soul Crystal Themes",
      "pitch": "Terminal palettes re-derive live from your job, HUD theme and chat colors, and the prompt can show the real job icon.",
      "how": "Set GHOSTTY_TERMINAL_OPT_COLOR_PALETTE with a per-frame lerp; lua/theme.lua builds 16 colors with a contrast clamp while app OSC overrides still win. Shim forwards ClassJob, ClassJobChanged and IGameConfig HUD/chat colors. Job icon drawn as a texture at a placeholder codepoint; prompt queries job via custom OSC.",
      "risks": "SeIconChar shares the PUA with Nerd Font powerline glyphs and ImWchar is 16-bit, hence textures. Job colors have poor contrast without clamping. Config option names need checking.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 113
    },
    {
      "mod": "xivarcade",
      "id": "arcade-handheld-bridge",
      "title": "Handheld Bridge",
      "pitch": "Setup profiles that make a Steam Deck or an Android RetroArch share the same synced save tree, so you can continue on the sofa.",
      "how": "The synced tree is already device-neutral: game_id is a hash of system and title, and system_cfg_text turns off per-core sorting. Add 'xiv-arcade sync-setup --profile deck|android' that prints the matching RetroArch overrides for that device (savefile_directory, savestate_directory, the sort flags) and the Syncthing folder path, like syncthing_folder_commands does today. The plugin shows it as an extra step in the first-run list.",
      "risks": "Android scoped storage and Syncthing forks make folder paths awkward. EmuDeck and similar setups sort saves per core and may overwrite the overrides. The handheld has no settle gate, so a game started there while saves arrive can still create a conflict.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 114
    },
    {
      "mod": "xivarcade",
      "id": "arcade-state-slots",
      "title": "Save State Snapshots",
      "pitch": "See your save states as screenshots in the details strip and start a game straight into the one you pick.",
      "how": "Turn on savestate_thumbnail_enable in system_cfg_text so RetroArch writes a PNG beside each state in states/<console>/, which already syncs. The helper lists states per game into state.json; ArcadeWindow loads the PNGs through the same background CoverCache thumbnails used for covers. A launch takes an optional slot and launch_argv passes RetroArch's entry-slot option.",
      "risks": "The entry-slot flag needs a recent RetroArch and must be checked against the Flatpak version before relying on it. State files are core-specific, so a state from Snes9x will not load in bsnes; show the core with each frame. PCSX2 and DuckStation keep states in their own formats and come later.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 115
    },
    {
      "mod": "xivdesktop",
      "id": "desk-controller-ring",
      "title": "Controller Ring Launcher",
      "pitch": "A radial app ring on the gamepad for Steam Deck and couch players, with favourites on the sticks and workspaces on the shoulder buttons.",
      "how": "Read IGamepadState in KeybindService alongside IKeyState and feed a pad chord into ChordTracker. The ring is a new ImGui window drawing UserLists favourites with IconDrawer; stick angle picks a sector. Launch and workspace steps go through CommandRunner, so nothing new reaches Ghostty.",
      "risks": "Any button combo the ring uses is also read by the game; it must be a chord the player chooses and ideally one the game leaves unbound. Panel apps still want a keyboard, so this launches and arranges, it does not make a browser controller-friendly.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 116
    },
    {
      "mod": "xivdesktop",
      "id": "desk-glamour-plate-layouts",
      "title": "Glamour Plates for Your Desk",
      "pitch": "Save a named set of apps and placements, and put the whole arrangement on with one palette entry.",
      "how": "A Layout record in Core (app id, workspace, pin args, order) with a pure planner that diffs it against the current window list, the way WorkspaceModel.Plan already diffs visibility. Applying launches missing apps through window.open, waits for request ids to resolve in window.list's requests, then issues window.place and the workspace moves. Saved in Configuration beside Workspaces; exposed as palette rows and an additive XivDesktop.v1 gate.",
      "risks": "Windows appear asynchronously and some apps open several windows or a splash first, so matching window to app is fuzzy (WindowApps helps). Placement arguments are relative to the character, so the same plate lands differently in different spots. Launching six apps at once is slow.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 117
    },
    {
      "mod": "xivdesktop",
      "id": "desk-zone-layouts",
      "title": "The Desk Knows Where You Are",
      "pitch": "Bind a layout or workspace to a territory, so your house, the inn and Limsa each get their own arrangement back when you arrive.",
      "how": "Subscribe to IClientState.TerritoryChanged in SessionService (AskService already does). A small Core map from territory id, and for housing the ward and plot where the client state exposes it, to a plate name or workspace number. On change, run the same layout planner as named plates. The Configuration gains a per-territory table; ARCHITECTURE.md already lists this as planned.",
      "risks": "Zone changes happen while panels are mid-request; everything must wait until the load finishes. Housing identifiers are less stable than territory ids across patches. Instanced areas share a territory, so the match may be coarser than the player expects.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 118
    },
    {
      "mod": "xivmcp",
      "id": "mcp-ask-the-player",
      "title": "An Agent May Ask You One Question",
      "pitch": "A Ui tool that puts a short multiple-choice question on your screen and hands the answer back, so an agent stops guessing which gear set or which retainer you meant.",
      "how": "A new ask_player tool (Ui tier, marked RequiresApproval=false as it only displays to the owner, listed in ToolContractTests' display-only set) takes a question and two to five choices, returns a question id at once and exposes ffxiv://questions/{id} with notifications/resources/updated on the answer, the same shape as tickets. The window reuses ConfirmWindow's layout and the pull-safe hold for combat. MCP's own elicitation/create is a later option once clients render it; McpServer.Dispatch has no handler for it today.",
      "risks": "A chatty agent could ask twenty questions an hour; it needs a per-client cap and a 'stop asking' button. Answers are free text to the model, so choices must stay short labels, never a way to route the player into a gated action without its own prompt. Clients that do not subscribe to resources must poll.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 119
    },
    {
      "mod": "xivpiano",
      "id": "piano-station-modes",
      "title": "Station Modes",
      "pitch": "Pick a station's mode (My Station, Crowd Faves, Deep Cuts, Discovery, Newly Released, Artist Only) where Pandora offers them to your account.",
      "how": "pianobar asks for modes with interactiveradio.v1.getAvailableModesSimple (stationId) and sets one with interactiveradio.v1.setAndGetAvailableModes (stationId, modeId), through the same JSON API and Blowfish wrapping PandoraClient.CallAsync already does. A mode picker beside the station name in MainWindow.DrawNowPlaying; a mode change clears Radio's queue so the next song follows it.",
      "risks": "These calls are newer than the rest and less documented; whether they answer for the Android and Pandora One partners XivPiano signs in as, and for free accounts, has to be found out first. Pandora offers different modes per station, so the picker must list only what the answer says.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 120
    },
    {
      "mod": "xivwayfinder",
      "id": "way-aether-currents",
      "title": "The Next Aether Current",
      "pitch": "Point at the nearest aether current in this zone that you have not yet attuned to.",
      "how": "The AetherCurrentCompFlgSet sheet lists each zone's currents; field currents are EObj placements found through Level rows. FFXIVClientStructs' PlayerState reports which currents are unlocked. A new source (or a queue fed into way-waypoint-queue) keeps the nearest unattuned one as the target, re-read at the same 0.25 s scan Plugin.Update already does for the flag and quest.",
      "risks": "Quest-rewarded currents have no field position and must be named, not pointed at. Plugins that already do this exist; this would need to be clearly nicer, not merely present. The EObj-to-current mapping has to be checked in game.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 121
    },
    {
      "mod": "xivwayfinder",
      "id": "way-flight-paths",
      "title": "Pointing on the Wing",
      "pitch": "When you are mounted and flying, ask vnavmesh for a flight path and let the bead climb and dive with it.",
      "how": "Navmesh.Request always passes fly = false to vnavmesh.Nav.PathfindCancelable. Pass true when ICondition shows InFlight (and Diving under water), and requery on take-off and landing through PathFollow.ShouldRequery. Heading.Ahead gains a pitched variant so the bead follows the path's height; the glove stays a ground-plane pointer with an up or down hint.",
      "risks": "vnavmesh's flight meshes are less tested than its ground meshes. A flying path to a flag without height has no obvious end altitude. Must stay pointing only: never vnavmesh's movement gates, in the air as on the ground.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 122
    },
    {
      "mod": "xivwayfinder",
      "id": "way-quest-map-links",
      "title": "The Quest Continues Next Door",
      "pitch": "When a quest step lies on another map, point at the exit the game's own quest link names instead of just saying 'another zone'.",
      "how": "docs/WAYFINDER.md lists AgentMap.MapQuestLinkContainer (QuestId, LevelId, source and target map) as 'not used yet'. GameReader.Quest reads it when no marker or to-do Level is in the player's zone and returns the link's Level as the target, labelled with the destination. Core gains a pure chooser beside QuestPick.Nearest, host-tested.",
      "risks": "The container reflects what the map has loaded, the same known gap the event markers have. Links can point at places the player can only reach by ferry or NPC. Needs checking against a real journal, which nothing in the quest reads has had yet.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 123
    },
    {
      "mod": "almanac",
      "id": "almanac-context-fit-finder",
      "title": "Context Fit Finder",
      "pitch": "A wizard step that tries the chosen model at 8k, 16k and 32k context, measures VRAM and speed at each, and recommends the largest that does not spill into system RAM.",
      "how": "SetupWindow already computes the VRAM budget (GpuInfo, GameReserveMb) and Engine.BenchRunnerAsync builds a runner with an IVramProbe (OllamaVramProbe first, NvidiaSmiProbe as fallback) sampled by VramSampler. Run a three-task subset of Suite.Bundled() with Ollama's num_ctx option per step, record peak VRAM and tok/s, and detect spill as a sharp drop in speed or Ollama reporting size_vram below size.",
      "risks": "Only Ollama takes a per-request context size; LM Studio and llama.cpp fix it at load time, so for them this can only advise. Each step reloads the model, which takes time and briefly competes with the game for VRAM. Under Wine, nvidia-smi may not be reachable from inside the game.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 124
    },
    {
      "mod": "almanac",
      "id": "almanac-duty-residency",
      "title": "Give the VRAM Back for Duties",
      "pitch": "When the model shares the game's GPU, unload it as you enter a duty and load it again when you leave, so savage frame rates do not pay for an idle chatbot.",
      "how": "Only when AlmanacSettings.GameOnSameGpu is set. For an Ollama backend (BackendKind), POST /api/generate with keep_alive 0 on BoundByDuty and a no-prompt load with keep_alive -1 on leaving, the same calls the engine's residency loop makes. The ServerDetector root URL and Engine.Quick HttpClient already exist for this. Skip it while ChatSession is Busy, and show the state in the chat header.",
      "risks": "With the Almanac gateway as the engine, its own residency loop pins the model while ffxiv_dx11.exe runs and will fight the plugin, so the gateway needs a way to be told, or the plugin must stay out of it. LM Studio and llama.cpp have no equivalent API. Reloading a model right as the duty ends causes a stutter in town; a delay helps.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 125
    },
    {
      "mod": "almanac",
      "id": "almanac-headroom-gauge",
      "title": "Headroom Gauge",
      "pitch": "A live readout of VRAM used by the game and the model, and of the last reply's tokens per second, so you can see a hub or a busy FATE pushing the model out.",
      "how": "Reuse IVramProbe from Bench/Vram.cs on a slow timer (every few seconds, off the framework thread), plus DXGI's QueryVideoMemoryInfo next to the adapter enumeration in GpuInfo.cs for the game's own budget. Tokens per second come from ChatResult's OutputTokens and elapsed time, already measured for the benchmark. ChatWindow.DrawHeader shows it; thresholds come from GameReserveMb.",
      "risks": "QueryVideoMemoryInfo through DXVK under Wine may report numbers that do not match the real card. nvidia-smi polling costs a process spawn each time; keep it slow or use the Ollama probe. Numbers that jump about will worry people more than help them, so smoothing matters.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 126
    },
    {
      "mod": "almanac",
      "id": "almanac-notes-tool-source",
      "title": "Your Own Notes as a Tool",
      "pitch": "Point Almanac at a folder of Markdown notes, such as your glamour plans, retainer ventures or static's strats, and the model can search them in game.",
      "how": "A NotesToolSource implementing IToolSource with two read-only tools, notes_search and notes_read, backed by an FTS5 index in almanac.sqlite (the Python engine's knowledge base does the same with SQLite FTS5, so its front-matter format can be reused). Engine.NewLoop adds it to the ToolHub list; ToolProfiles gains the two names in SmallSet so small models see it. Re-index on a file-change watcher or on a Refresh button in SettingsWindow.",
      "risks": "Notes go to whatever model server is configured, which may not be local; the settings page has to say that. A large folder makes results long and eats a small context (see the context gauge). A file watcher under Wine on a Z: path may not fire, so manual refresh has to exist anyway.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 127
    },
    {
      "mod": "ghostty",
      "id": "accessibility-inheritance",
      "title": "Inherits Your Accessibility Settings",
      "pitch": "The terminal follows the game's color-blindness filter, supports accessible fonts per tab, and can speak matching output lines.",
      "how": "CVD-safe palette preset chosen from a forwarded IGameConfig color-filter option. Per-profile font handles via AddFontFromFile. Agent strips escapes, regex-matches lines and sends a small frame; speech via host-side piper/espeak, SAPI or TextToTalk IPC as options.",
      "risks": "No global high-contrast mode to inherit; offer a manual AAA palette. SAPI under Wine is poor, so prefer host TTS. Rate-limit speech so it never talks over callouts.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 128
    },
    {
      "mod": "ghostty",
      "id": "gaze-and-presence",
      "title": "Gaze and Presence",
      "pitch": "Your character turns to the focused panel, startles when a background terminal rings, and dozes after long idle.",
      "how": "Body turn exists (rate-limited set_rotation); trigger on focus change. Bell already bound in ghostty.nelua; emit on_bell to Lua for a startle timeline, turn and backlight pulse. Doze is an idle timer in animation.lua. Head look-at is research and deferred.",
      "risks": "Merge with the in-progress visual bell rather than building twice. Only rotate while stationary. Look-at conflicts with posing plugins.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 129
    },
    {
      "mod": "ghostty",
      "id": "market-board-hover",
      "title": "Hover-to-Shell Market Board",
      "pitch": "Hovered items stream into your shell, so a Universalis CLI draws cross-world price history beside the retainer bell.",
      "how": "Shim forwards IGameGui.HoveredItemChanged and home DC via gu_event; Nelua debounces ~150ms and relays PROTO_EVENT. `term events --kind hover` feeds a shell script calling Universalis. Panel shown while OccupiedSummoningBell.",
      "risks": "Not every row raises a tooltip. Universalis is crowd-sourced and rate-limited. Existing plugins already show prices; the draw is your own tooling. Never write prices back.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 130
    },
    {
      "mod": "ghostty",
      "id": "xhb-shell-slots",
      "title": "Live Cross Hotbar Shell Slots",
      "pitch": "Hotbar slots bound to shell jobs show live progress rings, so a Steam Deck player can watch a build without opening a terminal.",
      "how": "Shim forwards _ActionCross/_ActionBar slot rects plus RaptureHotbarModule and macro text. Nelua maps `/term focus <name>` slots (needs named sessions) and draws rings on the foreground list. `term slot` arrives as PROTO_EVENT. Hold detection from GamepadDown plus active set.",
      "risks": "HUD layouts, WXHB and scale plugins break slot rects. Macro fires on press before hold. Niche for the effort.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 131
    },
    {
      "mod": "xivdesktop",
      "id": "desk-open-with-file",
      "title": "Open With, from inside the game",
      "pitch": "Pick a file in an in-game file dialog and open it in the right host app, using the %f and %u codes the Exec parser already knows about.",
      "how": "ExecLine.StripFieldCodes currently expands %f %F %u %U to nothing; add a variant that substitutes one or many shell-quoted Linux paths. DesktopEntryParser keeps MimeType; AppCatalog builds a mime index. The picker is Dalamud's FileDialogManager over Z:\\, and HostPaths maps the chosen Wine path back to the Linux path before it goes to window.open. A new PaletteCommand kind 'open' and a right-click 'Open file with…' in LauncherWindow.",
      "risks": "Mime detection needs a guess from the extension or the agent running xdg-mime; Wine cannot see everything the host can. Flatpak apps need their --file-forwarding markers kept rather than dropped. Paths with odd characters are exactly where quoting bugs hide, so this needs fuzz tests beside FuzzTests.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 132
    },
    {
      "mod": "xivlantern",
      "id": "lantern-lfg-tags",
      "title": "Up For It Tags",
      "pitch": "Tick a few typed tags (maps, roulettes, crafting, ex, chat) and friends can filter the list to people who want the same thing tonight.",
      "how": "Extend Wire::Heartbeat in crates/lantern-core/src/node/friends.rs with a tags field, or better add a new Heartbeat2 variant at the end of the enum so old nodes keep decoding, and store tags beside status/note for each friend device. Surface them in lt_friend_list JSON, FriendView and GetFriends over IPC. MainWindow.DrawList gains a filter chip row. Tags are chosen from a fixed list plus free text the player types; nothing comes from the game.",
      "risks": "A wire change needs a compatibility plan: postcard enums break if variants are reordered, and mixed versions will run side by side for weeks. Free-text tags need the same clamping and ChatText.Clean treatment as notes. Tempting to auto-fill from the duty finder, which the design forbids.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 133
    },
    {
      "mod": "xivlantern",
      "id": "lantern-share-screenshot",
      "title": "Pass a Picture Along",
      "pitch": "Share a screenshot or small file with a friend or channel, peer to peer, shown as a thumbnail you choose to open.",
      "how": "The network half exists: lt_blob_add_file, lt_blob_offer, lt_blob_fetch and LT_EV_BLOB_OFFER/PROGRESS/DONE are in lantern.h and wrapped by LanternNode.BlobAddFile/BlobOffer/BlobFetch. The plugin needs a picker (a path field or drag from the file system), an offer line in ChatBook, fetch on click into a folder under the plugin config directory, and a thumbnail through ITextureProvider for PNG and JPEG only. For 1:1, offer over the friend link rather than a room.",
      "risks": "Decoding untrusted images in the game process is a crash and exploit surface; fetch must stay click-to-accept, capped in size, and blocked or muted peers ignored. Screenshots leak names and chat, so the offer dialog should say so. Blob storage grows on disk and needs a cleanup setting.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 134
    },
    {
      "mod": "xivmcp",
      "id": "mcp-craft-margin-sheet",
      "title": "What Is This Craft Worth",
      "pitch": "Cost to craft from the full recipe tree against recent sale prices on your data centre, with the age of every price shown.",
      "how": "Combine get_recipe_tree (RecipeTree), find_owned_items and get_market_prices (UniversalisClient behind MarketCache) into one tool or prompt: price leaves by median recent sale, mark owned quantities as free, and report the age of each price. The crafting_plan prompt gains a 'with prices' variant.",
      "risks": "Universalis data is crowd-sourced and can be hours stale; a thin market gives misleading medians. A deep tree asks for many items and must batch within the 20-item call and be polite with the cache. It stays on reading public prices and never touches the market board UI, per HARD-LINES.md; alerts on single listings are deliberately left out.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 135
    },
    {
      "mod": "xivmcp",
      "id": "mcp-gear-upgrade-path",
      "title": "Next Three Upgrades",
      "pitch": "For your current job, the three cheapest item-level gains you can reach, what each costs in tomestones, seals or crafted materials, and what you already hold towards it.",
      "how": "A plan_gear_upgrades tool (GameData, with a live half) takes get_equipment's slots, searches equippable items above each slot's level with the same index search_items uses, ranks by item level gain per unit cost from ItemSources (SpecialShop and GilShop rows) and RecipeTree, and compares against get_currencies and find_owned_items. The gear_audit prompt calls it.",
      "risks": "Real best-in-slot depends on stat tiers and substat weights the game data does not state; this ranks by item level and cost only, and must say so. SpecialShop parsing is the messiest data in the sheets. Raid drops are not in the sheets at all.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 136
    },
    {
      "mod": "xivpiano",
      "id": "piano-lantern-status",
      "title": "Now Playing on Your Lantern",
      "pitch": "With XivLantern installed, your friends can see what you are listening to next to your lit lantern, if you say so.",
      "how": "XivPiano already offers XivPiano.NowPlaying over IPC. XivLantern would gain an opt-in to read it and add 'listening to' to the presence note it already shares with friends; nothing in XivPiano changes except a new XivPiano.SongChanged message gate (see piano-song-events) so XivLantern does not have to poll. Off by default in both plugins.",
      "risks": "It needs work in XivLantern, which only has XivLantern.v1.GetStatus today, and XivLantern's promise is that nothing from the game is sent; a song title is not from the game, but it is still something about you going to others, so it must be plainly opt-in. Song titles can be long and need cutting.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 137
    },
    {
      "mod": "xivwayfinder",
      "id": "way-aethernet-last-leg",
      "title": "The Aethernet Leg",
      "pitch": "In the big cities, name the aethernet shard nearest the target as well: 'Teleport to Ul'dah, then aethernet to Sapphire Avenue Exchange'.",
      "how": "The Aetheryte sheet includes aethernet shards (not IsAetheryte, grouped by AethernetGroup), with MapMarker positions like the main aetherytes. Extend GameReader.AetherytesIn to return shards, and TargetPicker.NearestAetheryte to pick a (main, shard) pair when the shard saves real distance. Also useful in the same zone: 'aethernet to X' when the walk is long.",
      "risks": "Shard attunement state needs its own check, and it may not be exposed as simply as IAetheryteList. Cities span several territories (Ul'dah Steps of Nald and Thal), so the shard graph crosses zones. The note gets longer and must stay readable.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 138
    },
    {
      "mod": "xivwayfinder",
      "id": "way-hot-and-cold-chime",
      "title": "Warmer, Colder",
      "pitch": "An optional soft chime that repeats faster as you face the right way and slows as you turn away, for players who would rather listen than look.",
      "how": "Heading.TurnAway already gives 0 to 1 for how far off course you face. A new pure Chime class in XivWayfinder.Core maps that and the distance to an interval, host-tested like Pulse. The plugin plays a game UI sound through FFXIVClientStructs' UIGlobals.PlaySoundEffect on each tick. Settings: off by default, a sound choice from the chat <se.1>-<se.16> set, and a volume note pointing at the game's own System Sounds slider.",
      "risks": "The game's sound effects cannot be pitched, so only the rate can carry meaning. Repeated chimes get tiring fast; it must stop on arrival and in the same situations Visibility.Decide hides the pointer. UIGlobals signatures can move between patches.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 139
    },
    {
      "mod": "xivwayfinder",
      "id": "way-minimap-glove",
      "title": "A Glove on the Compass",
      "pitch": "A small glove clings to the edge of the minimap and points the way there too, for players who navigate by the minimap.",
      "how": "Read the _NaviMap addon's position, scale and rotation through IGameGui.GetAddonByName and its node tree. Place the glove on the ring with a circular version of ScreenEdge.RayToEdge (a new pure helper in Core, host-tested), and draw it with the existing Glove.Quad and GloveTexture, at a smaller size, on the same foreground draw list Overlay uses.",
      "risks": "The minimap can be locked north-up or to the camera, and its rotation must be read correctly for both. HUD layouts, scaling and minimap replacement plugins move or hide it. Addon node indices change between patches.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 140
    },
    {
      "mod": "xivwayfinder",
      "id": "way-sightseeing-vistas",
      "title": "Sightseeing Log Guide",
      "pitch": "Point at the next Sightseeing Log vista you have not logged, and say which weather and hours it needs.",
      "how": "The Adventure sheet has each vista's Level (place), emote and time window; PlayerState knows which are complete. A new source reads them for the current expansion's log. The existing DrawNote glass note carries the conditions. Pure selection logic goes in Core with host tests, like QuestPick.",
      "risks": "Weather conditions come from another sheet and forecasting them is its own project. Needs care to not spoil vistas for players who like finding them unaided, so it only points at the one you ask for.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 141
    },
    {
      "mod": "ghostty",
      "id": "linkpearl-bridge",
      "title": "Linkpearl Bridge",
      "pitch": "Bridge your FC's Matrix or IRC room into a linkshell-styled terminal that echoes lines only into your local chat log.",
      "how": "weechat script emits OSC 777 on activity; surface on_notify to Lua (or an agent NOTIFY frame). Shim forwards IChatGui.Print, which is local-only. Lua filters, colors and rate-limits with a clearly non-game tag.",
      "risks": "Low novelty. Chat logger plugins capture external messages. A tag resembling [LS1] invites replying in game chat. Throttle to avoid flooding mid-duty.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 142
    },
    {
      "mod": "xivarcade",
      "id": "arcade-almanac-hints",
      "title": "Stuck? Ask the Scholar",
      "pitch": "A spoiler-careful hint button for the game you are playing, answered by your local model through Almanac, with nothing leaving your machine.",
      "how": "ArcadeWindow's details strip gets a Hint box that calls Almanac's IPC with the game's title, console and year from ArcadeGame plus the player's own question and a spoiler level. XivArcade sends nothing else: no emulator memory, no screenshots. The reply opens in Almanac's own chat window.",
      "risks": "Local models know little about most 1990s games and will invent walkthrough steps; the answer needs a clear 'model guess' label. Depends on Almanac being installed and a model loaded. Adds one more optional cross-plugin IPC to test.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 143
    },
    {
      "mod": "xivarcade",
      "id": "arcade-dat-verify",
      "title": "Verified Dump Badges",
      "pitch": "Check your files against a No-Intro or Redump DAT you supply and mark good dumps, bad dumps and their canonical names.",
      "how": "The player drops Logiqx XML DAT files into a dats/ folder; nothing is downloaded. The helper hashes files in the background (SHA-1 and CRC32, cached in the games table by size and mtime), matches them, and puts the verdict and canonical name into state.json. art_candidates tries the canonical name first. Uses the same content-check approach as check_bios_file.",
      "risks": "Hashing a PS2 library takes a long time and must be resumable and throttled. CHD and CSO are compressed, so their hashes do not match Redump without chdman or decompression; say 'not checkable' rather than guess. DAT files have their own licensing and must never be bundled.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 144
    },
    {
      "mod": "xivlantern",
      "id": "lantern-invite-by-npub",
      "title": "Invite by Name Card",
      "pitch": "Send a friend invite to someone's nostr npub or name@domain instead of pasting a ticket through a tell or Discord.",
      "how": "nostr_send_invite and nostr_check_inbox are implemented and tested against an in-process relay (tests/nostr.rs), raising NostrInvite without redeeming anything. Add them to lantern-ffi as new LT_EV_* events, NIP-05 resolution as DESIGN.md describes, and an npub field plus pending list in MainWindow.DrawAdd. Accepting still goes through AcceptInvite, so nothing is accepted for the player.",
      "risks": "A public npub invites spam; rate limits and ignore must apply to nostr invites too. NIP-05 depends on someone's web server being up. It adds a third party (nostr relays) that the privacy note must name.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 145
    },
    {
      "mod": "xivlantern",
      "id": "lantern-move-house",
      "title": "Moving House",
      "pitch": "Export your identity and friend list to one encrypted file and import it on a new PC, so friends do not have to re-add you.",
      "how": "The store already holds everything: identity, profile, friends, friend_devices, blocked. Add export and import to lantern-core (a passphrase-encrypted SQLite backup plus a schema version check), expose them in lantern-ffi, and put two buttons in MainWindow.DrawSettings. Saved channel tickets live in the Dalamud Configuration and would be included from the C# side.",
      "risks": "Running two copies of the same NodeId at once confuses friends' links and presence; import should refuse while another node with that id is online, which is hard to know. The export file is a full identity and must be treated like a private key.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 146
    },
    {
      "mod": "xivmcp",
      "id": "mcp-per-client-read-scopes",
      "title": "Keys That Open Only Some Doors",
      "pitch": "Each per-client token carries its own list of categories, so the benchmark client can read game data but never your chat or your bags.",
      "how": "ClientTokenEntry (AutoApprovePolicy.cs) gains an optional category allow-list, edited in MainWindow.Policy.cs and settable through XivMcp.ConnectClient. The request scope already knows which token authenticated; tools/list, resources/list and dispatch filter by it after HostState's global tiers and categories, so a scope can only narrow. Changing a token's scope sends list_changed to that client's sessions only.",
      "risks": "Core/Abstractions and McpServer's public surface are frozen, so this must be an additive hook. Clients without a per-client token share the global view, which the UI must make obvious. Resources and templates cut across categories (ffxiv://chat/recent) and need their own mapping. It is a boundary between clients, not a sandbox for a hostile one.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 147
    },
    {
      "mod": "xivpiano",
      "id": "piano-crossfade",
      "title": "Crossfade Between Songs",
      "pitch": "Fade the end of one song into the start of the next, so the radio flows like a mix instead of stopping and starting.",
      "how": "AudioOut today holds one WaveOutEvent and one MpegSampleProvider. It would build one output over a MixingSampleProvider fed by two MpegSampleProviders, with a PrepareAsync(Track) that downloads the next song while the current one plays (Radio.AdvanceAsync already keeps one song in hand in its queue). A fade of a few seconds starts when Position reaches Duration minus the fade length, and the Ended event moves to the fade start so Radio advances then. Crossfade length and 'off' go in Configuration.",
      "risks": "Two songs in memory at once, a few MB each, which is fine. Thumbs and 'why this song?' act on Radio.Current, so the switch-over moment must be clear or a thumb could land on the wrong song. Replay gain differs per song; each keeps its own gain factor. Songs with long silent endings make the fade point a guess.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 148
    },
    {
      "mod": "xivpiano",
      "id": "piano-scrobble",
      "title": "Scrobble to Last.fm",
      "pitch": "Send what you listen to in game to your Last.fm profile, as Elpis does, if you sign in to Last.fm.",
      "how": "A small Last.fm client in XivPiano.Core (Elpis has one in Libs/LastFmScrobbler): desktop auth once, then track.updateNowPlaying when Radio.Current changes and track.scrobble when a song has played for half its length or four minutes, measured from AudioOut.Position, queued and retried while offline. The session key is kept with Secrets (DPAPI) like the Pandora password. Needs a Last.fm API key registered for XivPiano.",
      "risks": "A second network destination, which the privacy note must name, and off by default. Last.fm's own terms apply to the API key. Skipped and banned songs must not scrobble. Scrobble rules about minimum length (30 seconds) must be kept.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 149
    },
    {
      "mod": "xivdesktop",
      "id": "desk-svg-icons",
      "title": "Real Icons, Not Letter Tiles",
      "pitch": "Have ghostty-agent rasterise SVG-only icons once on the host so the grid and taskbar stop falling back to letter tiles.",
      "how": "IconResolver already reports IconKind.SvgOnly. Collect those names after a scan and ask the agent for a PNG cache path per name at Configuration.IconSize (a new agent method, rendered with whatever the host has, such as rsvg); cache under XDG_CACHE_HOME keyed by source path and mtime, then map through HostPaths.ToLocal so ITextureProvider opens it. No new dependency in the plugin.",
      "risks": "Needs a ghostty-dalamud and agent change, so it only helps players on a matching build. Without the agent, letter tiles remain. Icon themes vary; the cache has to be invalidated when themes change.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 150
    },
    {
      "mod": "xivlantern",
      "id": "lantern-key-passphrase",
      "title": "Lock the Lantern",
      "pitch": "Optionally protect the device key (and nostr key) in the node's database with a passphrase, entered when you turn Lantern on.",
      "how": "DESIGN.md's threat model says the key sits unencrypted like an SSH key with no passphrase. Wrap the identity row and the nostr_secret profile row with a key derived by Argon2id, add an unlock parameter to lt_config (an lt_node_open error code for 'locked'), and a passphrase prompt in MainWindow.DrawWelcome before LanternService.Start. Existing databases migrate on first set.",
      "risks": "A forgotten passphrase loses the identity for good; there is deliberately no recovery. Auto-start at plugin load (config.Enabled) can no longer be silent. The ABI version must bump.",
      "feasibility": "stretch",
      "effort": "days",
      "topPick": false,
      "value": 4,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 151
    },
    {
      "mod": "ghostty",
      "id": "agent-party-formation",
      "title": "Your Agents Are a Light Party",
      "pitch": "Parallel Claude Code agents fight at your side as a light party: tank plans, healer reviews, two DPS implement in their own worktrees, with context as MP and every tool call as a cast.",
      "how": "`ghostty-party` reads a composition from lua/party.lua (role, job icon, prompt file, worktree), runs `git worktree add` per member, and OPENs `claude` with a CWD env entry and a GHOSTTY_SESSION variable. Claude Code hooks (PreToolUse, PostToolUse, Notification, PreCompact, Stop, SubagentStop) call a tiny `ghostty-emit` that writes to a new unix socket on ghostty-agent, which buffers EVENT frames for detached sessions and broadcasts them; MP comes from the last usage entry in the hook's transcript_path. Formation is a new anchor preset on the pet springs in lua/world.lua, and frames are ImGui with job icons from ITextureProvider.GetFromGameIcon. Agents running outside Ghostty join through an in-game MCP server plugin: the shim subscribes to that plugin's agent-board IPC and change event and turns its status posts into frames. The LB button only types `git merge` into the tank's session with `/term type`, never Enter.",
      "risks": "Four live agents cost real money and quota, so the party list shows spend. Hook payloads and tool names change between Claude Code releases. Sharing with the FC puts code, prompts and secrets on other clients, and every attached client can write today, so it needs a read-only relay, a share toggle and redaction. Worktrees pile up and need an explicit disband. Inside duties the formation collapses to frames only. No in-game MCP server has been observed working yet.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 152
    },
    {
      "mod": "ghostty",
      "id": "bomb-defusal-night",
      "title": "Keep Typing and Nobody Derezzes",
      "pitch": "An asymmetric bomb-defusal party game: one player's terminal holds the ticking bomb and everyone else's holds a different page of the manual.",
      "how": "A game-master script on the host's agent generates a puzzle seed, then opens one bomb session and one manual session per reader. Each invite carries a token scoped to one role's session (read-write for the defuser, read-only for readers) and the crate's anchor as a save_state snippet. The bomb TUI emits `ESC _ ffxiv;bomb;strike=1;t=150 ESC \\`, which each client catches through OPT_UNKNOWN_SEQUENCE. Lua turns a strike into a red-tinted `sonar` bell style at the crate anchor, a world.nelua glitch on every panel and a short camera jitter through the existing GetCameraState/SetCameraState path. Detonation plays `burst` and derezzes every panel for a beat. Players talk only over voice or chat they type themselves.",
      "risks": "Scoped and read-only tokens don't exist yet; today's single token grants a shell. Voice chat is effectively required. Puzzle design is real game-design work. The camera jitter needs a motion-sickness toggle and never fires InCombat or BoundByDuty. Guest sessions need the same sandboxing as any shared shell.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 153
    },
    {
      "mod": "ghostty",
      "id": "keystroke-colosseum",
      "title": "Keystroke Colosseum",
      "pitch": "Vimgolf duels fought on two towering arena panels, where every keystroke drains your own HP bar and the stands get instant replays.",
      "how": "A duel host on the agent opens both contestant sessions from a challenge file inside a container, counts DATA frames per session as keystrokes and runs a checker when the editor exits. Spectators ATTACH to both sessions, and the agent already fans output to every attached client (agent.nelua broadcast). Counts, clock and result arrive as `ffxiv;duel` APCs through OPT_UNKNOWN_SEQUENCE. Lua places both panels with `at` anchors from a shared arena origin and draws HP bars with new ui progress widgets. For instant replay each spectator's client calls ghostty_snapshot_encode_buf once a second per duel terminal into a 15 s ring, and replay decodes each frame with ghostty_snapshot_decoder_decode into a scratch terminal on a replay panel. The derez reuses world.nelua's rez effect and the `burst` bell preset.",
      "risks": "Every attached client can write and there's one token, so contestants need session-scoped tokens and spectators a read-only relay before this is safe. Snapshotting two terminals every second costs CPU and memory, so duel sessions use tiny scrollback. DATA frames don't map one-to-one to keystrokes (escape sequences, key repeat), and paste must be disabled or scored by bytes. Contestants run code on the duel box, so the sandbox is mandatory. Players without the plugin see two people typing at thin air.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 154
    },
    {
      "mod": "ghostty",
      "id": "process-skyline",
      "title": "Process Skyline",
      "pitch": "Your machine's process table rises as a neon city on your island: memory sets a tower's height, CPU lights its windows, and every fork raises a new building.",
      "how": "A helper on the agent host samples /proc every second and writes the changes as `ESC _ ffxiv;procs;<pid ppid rss cpu state comm> ESC \\` into a hidden session kept live with run_occluded, caught through OPT_UNKNOWN_SEQUENCE with OPT_UNKNOWN_MAX_BYTES raised. Lua lays out a treemap by parent PID at a `here` anchor, so systemd slices become districts, and tower heights move on the same springs world.lua uses for pets. Towers are extruded quads through world_transform_vertices with windows as lit dotted rects, tinted by world.lighting(). Hovering uses ray-box tests built on the world_hit maths, and double-click opens a panel whose profile runs `htop -p <pid>`.",
      "risks": "Hundreds of processes means hundreds of extruded ImGui boxes, so threads merge, small processes bucket into suburbs and draw calls are capped. Extrusions have no depth test unless routed through the depth pass, so the city shows through hills. Command lines leak tokens in argv, so arguments are hidden by default. On a busy desktop it's just a Chrome city without a filter.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 155
    },
    {
      "mod": "ghostty",
      "id": "venue-lighting-desk",
      "title": "The Lighting Desk",
      "pitch": "A tracker or cue sheet in a terminal drives a rig of real in-game lights channel by channel, and opted-in patrons at the venue see the same show on their own clients.",
      "how": "On the host, `ffxiv-lightd` reads the tracker's MIDI out (ALSA sequencer or JACK) or a cue script and prints `ESC _ ffxiv;cue;ch=1;n=36;v=127;t=183.250 ESC \\` into a desk session; the APCs never touch the grid and arrive through OPT_UNKNOWN_SEQUENCE only on sessions flagged as desks. A new lua/lightrig.lua maps channels to fixtures (offset from a pinned `here` anchor, colour, range, shadows, attack and decay), and app/lights.nelua grows from one light per panel into a capped pool on the same LightCreate/LightUpdate calls (HostApi.cs:400-451). Patrons ATTACH to the desk session: the agent already broadcasts output to every attached client and replays 256 KiB on join. Cues carry desk time, and a new agent timestamp frame lets clients schedule against a playout buffer of about 150 ms. Fixture offsets hang off the house's ward, plot and room, which an in-game MCP server plugin can already read through HousingManager.",
      "risks": "Every attached client can write today and there's a single token, so patrons need a read-only broadcast relay first. Nobody knows how many Scene.Lights the renderer tolerates, so cap the pool at 8-16. Strobes need a hard rate limit and a per-patron intensity slider. Sync is only as good as each patron's latency, and the music reaches them over Discord or Twitch with its own drift. Players without the plugin see a normal club. Aetherial Visualizer pulses panels from local audio; this is an authored, shared show.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 156
    },
    {
      "mod": "xivarcade",
      "id": "arcade-lantern-netplay",
      "title": "Link Cable over Lantern",
      "pitch": "Invite a friend from XivLantern to a RetroArch netplay session of a game you both own, with the connection details passed privately between you.",
      "how": "The helper's launch_argv gains RetroArch's host and connect netplay modes. XivArcade offers a new IPC call beside XivArcade.v1.Search and Launch that builds an invite: game_id, core name and version, content CRC and a tailnet or LAN address, never the file. XivLantern carries it peer to peer, and the receiving XivArcade checks that the same game_id with the same CRC is in its own library before offering Join.",
      "risks": "The hard line holds: no game file, disc or BIOS ever travels, and a CRC mismatch refuses. NAT traversal is hard without Tailscale or a relay. Netplay needs identical core versions and breaks with standalone emulators. Needs a Lantern IPC hook that does not exist yet, and RetroArch netplay latency inside a headless compositor is unknown.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 157
    },
    {
      "mod": "xivlantern",
      "id": "lantern-voice-sidecar",
      "title": "Ring a Friend",
      "pitch": "One-to-one voice calls: ring from the friend list, and the audio runs in a small companion process beside the game.",
      "how": "Follows DESIGN.md's call plan. Signalling already exists as a spike behind the calls feature (node/calls.rs: CallRing, CallAnswer, CallHangup with an iroh://<node>/call/<id> media URL); it needs C ABI events and a MainWindow call bar. Media runs in a lantern-media sidecar using moq-native with iroh and moq-audio against PipeWire (or WASAPI on Windows), paired to the plugin's node by a one-time ticket like a friend invite and driven over a loopback friend link.",
      "risks": "None of the media path has been built; DESIGN.md lists four observable steps before anything is in the game. Shipping and starting a second executable from a Dalamud plugin is unusual and must be explicit to the player. Echo cancellation, NAT traversal for UDP media and latency are all unmeasured.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 158
    },
    {
      "mod": "ghostty",
      "id": "homelab-server-room",
      "title": "The Server Room",
      "pitch": "One glowing panel per homelab node, pinned in your house or to housing furniture, with backlight and derez driven by live health checks.",
      "how": "Health comes from a hidden capture session or agent probes (ping/tcp/http, node_exporter, incus list) via a new STATUS/EVENT frame, never from the panels, which sleep. Lua maps host to ok/warn/down and sets per-panel tint/backlight; move rez from global host.world_rez to per session in worldview.nelua. Placement uses house-id-aware anchors (see furniture-anchors). Drill-in reuses click focus, fly-in and per-host ssh profiles. Shared view uses read-only guest tokens (see over-the-shoulder-pets).",
      "risks": "No depth test, so panels show through walls. A dozen live btop panels cost real FPS. Housing structs move between patches. Probe flaps cause alert fatigue; this is not a pager. Showing dashboards to an FC leaks hostnames and IPs.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 159
    },
    {
      "mod": "ghostty",
      "id": "item-links",
      "title": "Item Links in stdout",
      "pitch": "OSC 8 links with an ffxiv:// scheme turn CLI output into item tooltips, map flags and chat-ready item links.",
      "how": "Mouse to cell to GhosttyGridRef, then ghostty_grid_ref_hyperlink_uri, sharing mouse-selection plumbing. Lua parses item/map/quest URIs into typed actions. Shim forwards Lumina Item lookup and icon, OpenMapWithMapLink, and a chat-input item link insert. ImGui tooltip first; native tooltip later.",
      "risks": "Output is untrusted: typed actions only, click required, never free text into chat. Chat-input insertion depends on unstable ClientStructs internals.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 160
    },
    {
      "mod": "ghostty",
      "id": "kitty-graphics-tapestries",
      "title": "Kitty Graphics Tapestries",
      "pitch": "Kitty graphics protocol images render on 3D panels, so live dashboards and pictures hang in the world like paintings.",
      "how": "libghostty-vt already implements kitty graphics placements. Decode PNG with vendored stb_image or Zig. Shim forwards CreateTexture/ReleaseTexture via ITextureProvider.CreateFromRaw, cached by image id. Draw placements as tessellated quads so world_transform_vertices curves and tints them; plain AddImage in 2D. No sixel.",
      "risks": "Images vanish past the replay ring on sleep or restart unless re-sent or cached agent-side. Cap texture memory in-process; create on the render thread and free on unload or crash.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 161
    },
    {
      "mod": "ghostty",
      "id": "pass-the-model-m",
      "title": "Pass the Model M",
      "pitch": "Pair-program with a party member in one tmux session, and the floating Model M flies to whoever holds the keys.",
      "how": "Agent already fans output to multiple attached clients. Add scoped guest tokens (session ids, ro/rw), a per-session driver, and DRIVER/PASS frames over a tailnet. Lua 'between' anchor at the midpoint of player and friend; Model M springs between them on driver events. Each client poses only its own character.",
      "risks": "Guest tokens are a new security boundary on a TLS-less agent, so tailnet only; rw means arbitrary code as the host user. Seeing your friend's pose locally is unproven. Depends on the in-progress Model M.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 162
    },
    {
      "mod": "ghostty",
      "id": "regional-phosphor",
      "title": "Allagan Phosphor",
      "pitch": "Every terminal re-renders in the technology of wherever you stand: Allagan green phosphor, Garlean magitek amber, Sharlayan hologram blue, Crystarium cut glass.",
      "how": "Draw each world panel's cells into an offscreen texture with ITextureProvider.CreateDrawListTexture, only when the render state reports GHOSTTY_RENDER_STATE_DIRTY_PARTIAL or FULL, so idle panels cost nothing. Draw that texture on the curved quad through the core/depthpass.nelua callbacks with a material shader built by tools/build-shaders.lua: barrel curvature, shadow mask, scanlines, bloom, and persistence blended with last frame's texture. The glass material also copies the panel's screen rect from the back buffer and offsets its samples by the cylinder normal panel_depth.hlsl already computes. A new lua/materials.lua maps TerritoryType through Lumina to material tables, world.place gains a `material` field, and zone changes crossfade. While DATA_MODE shows synchronized output (2026) the texture keeps its last frame so trails never smear a half-drawn screen. Cracks key off the agent-side CMD_END exit code.",
      "risks": "Render-to-texture adds a pass and texture memory per dirty panel, and it's unconfirmed that CreateDrawListTexture accepts a draw list built by the native core, so a direct-draw fallback stays. The back-buffer copy is taken after the HUD is drawn, so HUD pieces behind glass bend too. Curvature and bloom cost legibility, so a hold-to-read key snaps back to crisp. Persistence trails can bother motion-sensitive players. Nothing on the depth path has been observed in game.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 163
    },
    {
      "mod": "ghostty",
      "id": "two-person-rule",
      "title": "Two-Person Rule by Emote",
      "pitch": "Gate a dangerous command behind signed approvals from party members, with /thumbsup as the ritual.",
      "how": "Authority lives in the agent: held command, quorum policy, approver public keys, ed25519 verify via vendored monocypher over (hash, nonce, expiry), then forkpty. Frames PENDING/APPROVE/APPROVED. Approver's plugin watches only its own timeline for thumbsup (or /term approve), signs and sends over a guest connection.",
      "risks": "Keys inside the game process are readable by other plugins; pitch as oops-prevention, not security, with a hardware-key option. Enforce minimum view time on the diff. Real crypto in a hobby agent needs care.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 164
    },
    {
      "mod": "xivarcade",
      "id": "arcade-housing-cabinet",
      "title": "The Cabinet in the Corner",
      "pitch": "Give a game a fixed spot in your house or apartment, so it always opens on the same wall or furniture instead of wherever the panel lands.",
      "how": "Today ArcadeService.Play hands one shell line to HostLauncher.RunInCompositor, which sends window.open with only a run field, then finds the panel through window.list. Add an optional placement to GhosttyWire.OpenRun (anchor position, heading, size, territory and house id), stored per game in Configuration and captured with '/arcade cabinet here' from the player's own position. ghostty-dalamud needs a matching window.open parameter or a window.place call; its own furniture-anchor idea covers the house-id side.",
      "risks": "It depends on a ghostty-dalamud IPC change that does not exist yet. Housing layout and territory ids shift with patches, and panels have no depth test, so a cabinet can show through walls. The panel path has not been observed in game at all.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 165
    },
    {
      "mod": "almanac",
      "id": "almanac-suite-from-thread",
      "title": "Turn a Conversation into a Benchmark Task",
      "pitch": "When a model gets a real question badly wrong, turn that exchange into a proposed benchmark task, with the tool results frozen as fixtures, for the next suite version.",
      "how": "A thread already holds the user prompt, each tool call and its JSON result, which are the shape of a SuiteTask (Prompt, Tools, Expect) and of the suite's fixtures that FixtureToolSource replays. Build the draft task from AlmanacStore.Messages, let the player write the Expect rules in the vocabulary JsonRules and Scorer already use, and send it through the existing DeviceLink sign-in to a proposals endpoint, not straight into ffxiv-core.",
      "risks": "Fixtures captured live contain the character name, location history, retainers and possibly other players' names from chat tools; the form has to redact them and show the exact payload, as Share does. Every accepted task changes the suite's Sha256 and breaks comparability with past runs, so tasks must batch into versioned releases. Writing good Expect rules is hard for most players.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 166
    },
    {
      "mod": "ghostty",
      "id": "aetherial-visualizer",
      "title": "Aetherial Visualizer",
      "pitch": "Audio from a terminal's player makes panel backlights and pet terminals pulse to the beat.",
      "how": "Agent matches PipeWire nodes to the PTY process tree, captures with pw-record, runs a windowed FFT, and sends 8-16 bands as a BANDS frame at 30-60Hz. Lua modulates backlight and adds pet spring impulses; bars are extra projected quads.",
      "risks": "Audio over ssh plays remotely and can't be captured. Crowds see nothing unless synced. Needs intensity cap and photosensitivity toggle. Flatpak and pulse-compat matching is fragile.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 167
    },
    {
      "mod": "ghostty",
      "id": "ghost-furniture-blueprints",
      "title": "Blueprints with Ghost Furniture",
      "pitch": "Share a terminal installation as a paste-able blueprint, or import a MakePlace layout, that projects wireframe outlines where the furniture goes.",
      "how": "Builds on furniture-anchors. Lua serializes anchors plus item id and relative transform to base64 and imports MakePlace JSON. Nelua draws projected wireframe boxes sized from a per-item table. Match detection by item id within tolerance; template form in views.nelua; warm-up reuses rez.",
      "risks": "Must never write furniture positions: show a match indicator and let the player place. Shared commands are an RCE vector, so never auto-run and hash-pin blueprints. Only transfers between identical house sizes.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 168
    },
    {
      "mod": "ghostty",
      "id": "paged-mid-pull",
      "title": "Paged Mid-Pull",
      "pitch": "When your on-call healer gets paged, opted-in party members see a status line over them, so everyone knows why they went AFK.",
      "how": "Webhook receiver pushes a templated STATUS frame to the on-call's agent. Party members hold status-only guest tokens. Map name@world to entity via IPartyList/object table and project an ImGui label above the head. Incident terminal reuses over-the-shoulder-pets.",
      "risks": "Alert text leaks infra details; templated and redacted only. Ship as a mode of over-the-shoulder-pets and the bell, not a separate system. Hide labels in combat.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 169
    },
    {
      "mod": "xivlantern",
      "id": "lantern-channel-history",
      "title": "Catch Up on Join",
      "pitch": "Someone who joins or comes back to a channel gets the recent backlog from any member who has it, instead of an empty room.",
      "how": "DESIGN.md already names the shape: a blob snapshot offered by any member. Keep the signed gossip frames per channel in a new store table, and on a peer's Hello offer a snapshot with lt_blob_add_bytes and lt_blob_offer, which exist in the C ABI and the C# binding (BlobAddBytes, BlobOffer, BlobFetch) but are unused by the plugin today. The joiner fetches, re-verifies every frame with proto.rs, de-duplicates by signature and merges into ChatBook through Conversation.LoadHistory.",
      "risks": "Gossip gives no ordering, so merged history needs a sort by sender timestamp that a member could lie about. Every member keeps a copy, which makes 'delete' meaningless and should be said plainly. Snapshot size and how far back need limits, and a malicious member can offer junk, so verification must drop anything unsigned.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 170
    },
    {
      "mod": "xivlantern",
      "id": "lantern-fc-hearth",
      "title": "The FC Hearth",
      "pitch": "One container an FC can run on a spare box: an iroh relay, a moq relay for group calls, and an always-on node that keeps channel history.",
      "how": "Relay choice already supports LT_RELAY_CUSTOM with a URL (Configuration.CustomRelayUrl, Privacy.Relay). Package iroh-relay, moq-relay and a headless lantern-cli node (the CLI already runs author mode, so a 'keeper' mode that joins listed channels and serves history snapshots fits) as one OCI image with a compose file and a README of who sees what. Group calls use the relay for fan-out per the DESIGN.md plan.",
      "risks": "Whoever runs it sees IP addresses and timing for everyone who uses it, and holds every channel's plaintext if channels are not sealed; the privacy note must name them. Home hosting means port forwarding or tunnels. Keeping three services patched is a real commitment for a volunteer.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 171
    },
    {
      "mod": "xivlantern",
      "id": "lantern-nostr-mailbox",
      "title": "Leave It at the Post",
      "pitch": "When a friend has been offline for a while, your message also goes to their nostr inbox, so it reaches them even if you are both never online at once.",
      "how": "DESIGN.md specifies it: NIP-17 kind 14 rumor, sealed and gift-wrapped to the friend's DM relays, carrying the Lantern message id so the iroh copy is dropped as a duplicate. The nostr feature already sends invites this way (nostr_send_invite, nostr_check_inbox in node/nostr.rs); extend it to texts in the outbox, then expose nostr in the C ABI, which it is not today, and add a relay list to Configuration.",
      "risks": "Relays learn who receives messages, when and how big. It needs both people to have opted into nostr keys, and the nostr secret sits unencrypted in SQLite. Relays drop old events on their own schedule, so delivery is best effort and the UI must not promise more.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 172
    },
    {
      "mod": "xivmcp",
      "id": "mcp-plugin-tool-gate",
      "title": "Other Plugins Can Offer Tools",
      "pitch": "An IPC gate through which any Dalamud plugin registers its own read tools with XivMcp, under the same catalogue, rate limit and approval gate.",
      "how": "Core already describes tools by data through ExternalTool (McpServer.Extensions.cs), which the standalone host uses. A new IpcContract gate lets a plugin register ExternalTool descriptions whose handler calls back into that plugin's own IPC. They sit under a per-plugin category the player enables in Settings, and anything not Read goes through ConfirmationService like every other gated tool.",
      "risks": "XivMcp cannot see what another plugin's handler does, so HARD-LINES.md cannot be enforced on it; the first version should allow Read tier only, and say so. ToolContractTests cannot check runtime tools. A plugin that unloads leaves dead tools until list_changed. IpcContract is a frozen, additive-only contract.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 173
    },
    {
      "mod": "ghostty",
      "id": "commit-constellations",
      "title": "Commit Constellations",
      "pitch": "On clear in-game nights, your git history hangs in the sky as constellations.",
      "how": "Hidden agent session runs `git log --graph`. sky.lua lays nodes on a camera dome gated by GetEnvironment time, weather and rain. Stars drawn and hit-tested in worldview.nelua with the existing fly-in. Stretch: sky-only occlusion by sampling the depth buffer, as Pictomancy does.",
      "risks": "Without depth occlusion stars draw over rooftops and the illusion breaks. Large graphs don't read as constellations. Decorative and rarely looked at.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 174
    },
    {
      "mod": "ghostty",
      "id": "job-codex-terminals",
      "title": "Job Codex Terminals",
      "pitch": "The terminal's physical form follows your job: codex pages for Scholar, a star globe for Astrologian, a wrist hologram for Machinist.",
      "how": "Add ClassJob to GuObject. Lua style table per job selects frame, layout and pose. Nelua render variants: page-flip transition, spherical strip, rune/hologram overlays on existing scanline code. Page flip triggered by tmux window change. Bone anchoring is stretch; pet offset as fallback.",
      "risks": "Per-job art and tuning is a large surface for a niche payoff; ship two or three. Bone positions vary by race and scale.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 175
    },
    {
      "mod": "ghostty",
      "id": "obs-scene-conductor",
      "title": "OBS Scene Conductor",
      "pitch": "Duty and panel events switch OBS scenes, and stream triggers fire whitelisted cosmetic effects in game.",
      "how": "Shim forwards IDutyState and TerritoryChanged; core sends EVENT frames to the agent. A small sidecar speaks obs-websocket v5 and relays inbound events as EFFECT frames. lua/effects.lua maps them to whitelisted calls gated on InCombat and BoundByDuty.",
      "risks": "Viewer camera control is risky if a gate fails. The agent holds another credential and any network exposure widens a shell-granting service. Frame-locked browser overlays are dropped.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 176
    },
    {
      "mod": "xivdesktop",
      "id": "desk-app-notifications",
      "title": "Moogle Delivery for App Notifications",
      "pitch": "Show notifications from apps running in the compositor as Dalamud toasts, with the app's icon and a click that focuses its panel.",
      "how": "The agent owns org.freedesktop.Notifications on the session bus it gives its compositor's apps and forwards summary, body and app id as a new event in window.list or a separate method. SessionService already raises Dalamud notifications through INotificationManager for panel open and end; extend Notify with the app's icon from CatalogService and a click that runs PaletteCommand.Focus.",
      "risks": "Depends entirely on agent changes and on apps using the session bus the agent controls. Notification bodies are private and would show on stream; needs a 'title only' option. Rate limiting is needed for chatty apps.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 177
    },
    {
      "mod": "xivlantern",
      "id": "lantern-every-device",
      "title": "One Friend, Every Device",
      "pitch": "A friend who plays on a PC and a Steam Deck is one entry, and messages reach whichever device they are on.",
      "how": "The core already models friends with many devices (friends and friend_devices tables) and verifies nostr device lists (nostr_publish_devices, nostr_fetch_devices, the lantern-device-claim/1 signature). What is missing is text fan-out to all devices with de-duplication by message id across them, the nostr calls in lantern-ffi and include/lantern.h, and a 'this device / add a device' page in MainWindow.DrawSettings.",
      "risks": "Delivered and read state across devices gets confusing fast (delivered to the PC, read on the Deck). Adding a device means importing the nsec on each, which moves a secret between machines. DESIGN.md lists this as the known gap because it is real work.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 178
    },
    {
      "mod": "xivlantern",
      "id": "lantern-sealed-channels",
      "title": "Sealed Channels",
      "pitch": "Channel messages encrypted with a key carried in the ticket, so relays and nodes passing gossip along cannot read them.",
      "how": "Add a symmetric key to the ltroom ticket in ticket.rs and encrypt the payload inside the signed frame in proto.rs (XChaCha20-Poly1305, signature over the ciphertext). Scope::Custom topics stay random. 'Reseal' makes a new channel with a new key and sends ChannelInvite over each chosen friend link, which friends.rs already does. The plugin shows a lock in DrawList and the ticket wording changes in DrawAdd.",
      "risks": "Anyone holding the ticket still reads everything; this protects against the swarm, not against members. Removing a member is only a reseal, and old keys still read old history. A ticket format change needs a version byte and a clear message on old clients.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 179
    },
    {
      "mod": "xivwayfinder",
      "id": "way-walk-next-zone",
      "title": "Walk, Don't Teleport",
      "pitch": "When the target is one or two zones away on foot, point at the zone exit instead of naming an aetheryte.",
      "how": "Map-to-map links come from MapMarker rows of the map-link type, read the same way GameReader.AetherytesIn already reads aetheryte markers and converted with MapCoords.PixelToWorld. Build a zone graph once and find the shortest walk with a breadth-first search in Core (pure, host-tested). TargetPicker.Plan grows a WalkToExit guide kind; GetState's 'guide' field gains a value, documented in IPC.md.",
      "risks": "Links through ferries, airships and NPC dialogue are not walkable and are hard to tell apart from real exits. One-way drops and locked zones (before a quest opens them) will mislead. Deciding when walking beats teleporting is a judgement call, so the first version should only offer it for adjacent zones.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 180
    },
    {
      "mod": "almanac",
      "id": "almanac-question-router",
      "title": "Only the Tools the Question Needs",
      "pitch": "Before each turn, narrow the offered tools to the few that fit the question, so small models stop reaching for get_equipment when you asked about the weather.",
      "how": "ToolProfiles' own comment says small models do better with fewer tools; today the choice is a static small/standard/all set. Add a per-turn filter to ToolHub: score each ToolDef by keyword overlap and, where the server offers one, embeddings from an /v1/embeddings model, keep the top N plus a fixed core (get_location, get_player). Measure it with BenchRunner against the unfiltered sets before it becomes a default.",
      "risks": "Leaving out the tool a question needed is worse than offering too many: the model guesses. Embeddings need a second model loaded next to the chat model and the game. Changing the tool list between turns of one thread can confuse models that cache tool names, and it makes benchmark results incomparable unless recorded in the result.",
      "feasibility": "stretch",
      "effort": "weeks",
      "topPick": false,
      "value": 5,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 181
    },
    {
      "mod": "xivpiano",
      "id": "piano-media-keys",
      "title": "Media Keys and Hotkeys",
      "pitch": "Play, pause and skip with your keyboard's media keys, or a key of your choosing, while the game has focus.",
      "how": "Dalamud's IKeyState reads keys while the game window has focus; Plugin.OnUpdate would watch the media keys (VK_MEDIA_PLAY_PAUSE, VK_MEDIA_NEXT_TRACK) and any keys set in Configuration, and call Plugin.Control('toggle') or Control('next') on a fresh press. Elpis does the same outside a game with global hotkeys (Elpis/Hotkeys.cs); here only in-focus keys are planned.",
      "risks": "Whether Wine passes media keys to the game at all has to be found out, and the desktop's own media player may take them first. A bound key must not also trigger a game action, and IKeyState cannot stop the game seeing it, so plain keys are risky and chords are safer. Macros with /piano already cover most of this.",
      "feasibility": "research",
      "effort": "days",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 182
    },
    {
      "mod": "ghostty",
      "id": "magic-lantern-projector",
      "title": "Magic Lantern",
      "pitch": "Aim a panel like a projector and its terminal, text and kitty images alike, is thrown onto whatever it hits: walls, furniture, aetherytes and the people walking through the beam.",
      "how": "The projector's terminal is drawn into a texture (ITextureProvider.CreateDrawListTexture from the shim, or a D3D11 render target on the device GuSceneDepth.ui_device already exposes), with kitty images uploaded through the decode path Kitty Graphics Tapestries needs: GHOSTTY_SYS_OPT_DECODE_PNG, re-uploaded when GHOSTTY_KITTY_IMAGE_DATA_GENERATION changes. A full-screen quad then runs through the same draw-list callbacks as core/depthpass.nelua with a sibling of panel_depth.hlsl: it rebuilds each pixel's world position from the reversed-Z SceneDepth and the inverse view-projection rows that cbuffer already carries, moves it into the projector's frustum from its world.place() pose, and samples the texture with cone falloff and a facing term from depth derivatives. app/lights.nelua tints the panel's Scene.Light toward the frame's average colour, so the room's real lighting brightens too. Alt-drag and Shift-snap aim it.",
      "risks": "It adds colour over a finished frame; it isn't real lighting, and it lands behind water, glass and hair that don't write depth. Render-to-texture from the native core is unproven, and the depth path has only been checked in a Wine/DXVK probe. Kitty's shm medium doesn't cross the TCP agent, so video arrives as base64 frames: cap resolution and FPS. Flashing video needs a photosensitivity switch. Only you and your stream see it.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 10,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 183
    },
    {
      "mod": "ghostty",
      "id": "shader-sky",
      "title": "Paint the Sky",
      "pitch": "Pipe a pixel shader from your shell and it replaces the sky over Eorzea on your screen only, fed by Eorzea time, the weather and where your camera points.",
      "how": "A shell helper compiles the HLSL on the Linux host with the same pinned vkd3d-compiler tools/build-shaders.lua uses, so nothing compiles inside Wine, and sends the DXBC as `ESC _ ffxiv;sky;<base64> ESC \\`, caught through OPT_UNKNOWN_SEQUENCE with OPT_UNKNOWN_MAX_BYTES and OPT_APC_MAX_BYTES raised for the payload. The core checks the DXBC header, creates the pixel shader on GuSceneDepth.ui_device, and adds one full-screen quad at the start of the background draw list through the core/depthpass.nelua callback path, with SceneDepth still bound at t1. The shader keeps only pixels whose reversed-Z depth is 0 (the infinite far plane) and discards the rest. A fixed cbuffer supplies day seconds, rain and weather id from ghostty.env(), the camera-relative inverse view-projection rows panel_depth.hlsl already builds rays from, the depth sizes and the session's bell count.",
      "risks": "Nobody has checked that the sky really reads depth 0; `/term depth show` comes first. A painted sky hides the sun, moon and clouds, and effects that don't write depth leave odd edges. Shader bytecode from a shell runs on your GPU inside the game process, where a heavy shader can stall frames or remove the device, so accept it only from the focused session, time-box it, and ask before loading one from a remote host. Only you and your stream see it.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 184
    },
    {
      "mod": "ghostty",
      "id": "tablet-prop-shell",
      "title": "The Tablet Prop Shows Your Shell",
      "pitch": "The tablet or book your character holds shows your live terminal on the game model itself.",
      "how": "Render the terminal to an offscreen texture (ITextureProvider draw-list wrap or D3D11 from Nelua), walk Character to weapon CharacterBase to Material texture, and swap its shader resource view each frame; a Penumbra mod gives a clean UV region. Cheap fallback: forward the hand bone world position and pin a small curved panel there.",
      "risks": "Pointer-swapping game textures risks use-after-free crashes on zoning, redraws and Penumbra reloads. Shared texture shows your shell on every visible instance of that prop. Barely readable outside close-ups.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 9,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 185
    },
    {
      "mod": "almanac",
      "id": "almanac-push-to-talk",
      "title": "Push to Talk, Read Aloud",
      "pitch": "Hold a key, ask your question out loud, and hear the answer spoken, with speech recognition and voice both running on your own machine.",
      "how": "Capture microphone audio in the plugin (for example NAudio's WASAPI capture), send it to a local whisper.cpp server's /inference endpoint that ServerDetector could find like the model servers, and pass the text to ChatWindow.Send. For speech, stream the TextDeltaEvent text sentence by sentence to a local TTS server such as Piper and play it on a separate audio device. Settings hold the two endpoints and the key.",
      "risks": "Microphone capture inside the game process under Wine is unreliable, and audio APIs differ per system. Speech recognition and TTS take GPU or CPU next to the chat model and the game, which is exactly the budget the wizard is trying to protect. Picking up voice chat from a party must never happen by accident, so push to talk only, with a clear indicator.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 186
    },
    {
      "mod": "ghostty",
      "id": "homelab-minion-menagerie",
      "title": "Homelab Minion Menagerie",
      "pitch": "Each server gets a client-side creature that follows you and acts out its health.",
      "how": "Per-host health records over a new agent frame. Existing place_pet springs drive following. Ready fallback: ImGui billboard sprites on the same springs. Real version: client-side actors via ClientObjectManager or Brio IPC, with a BGCollision raycast for ground height and custom screen-space hit tests.",
      "risks": "Client-spawned actors outside GPose are a known crash source and a grey area; they must despawn before zoning. Brio spawn outside GPose is uncertain. Many actors cost performance.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 187
    },
    {
      "mod": "xivdesktop",
      "id": "desk-send-to-game",
      "title": "Send This Window to Eorzea",
      "pitch": "A GNOME Shell entry that hands a window already running on your desktop to the game as a panel.",
      "how": "Moving an already-mapped window into the agent's compositor is not possible as such; GUIDE.md's planned route is the agent capturing it through the desktop portal's screencast, as it does on Windows. A small Shell extension calls the agent (not Wine) with the window's identity; the agent starts a capture and the game gets a window.open with a match. XivDesktop's part is palette and taskbar support for captured panels.",
      "risks": "A captured window is a picture plus forwarded input, not a native panel; input forwarding through the portal is limited. The portal prompts the user every time on many setups. GNOME extension APIs change every release. Nothing of this exists yet.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 188
    },
    {
      "mod": "xivlantern",
      "id": "lantern-pocket",
      "title": "Lantern in Your Pocket",
      "pitch": "A small phone app that is one more of your devices, so you can see who is lit and answer a message away from the PC.",
      "how": "lantern-core is plain Rust with no game dependency, and lantern-cli already runs it headless. A mobile shell (iroh runs on Android and iOS) would reuse lantern-core, pair as another device through the nostr device list (lantern-every-device), and use the nostr mailbox for wake-ups instead of a push server. Start with the CLI on a Linux phone or Termux as the proof.",
      "risks": "Mobile OSes kill background sockets, so presence and delivery need the mailbox or a push service, which reintroduces a server. App store distribution is its own project. Keys on a phone widen where the identity can be stolen from.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 189
    },
    {
      "mod": "xivlantern",
      "id": "lantern-voice-in-process",
      "title": "Voice Without the Sidecar",
      "pitch": "Try running Opus capture and playback inside lantern.dll itself, under the game's Wine, so voice needs nothing else installed.",
      "how": "Step 5 of DESIGN.md's call plan: build moq-audio (unsafe-libopus, cpal, sonora AEC) for x86_64-pc-windows-gnu in the same container that builds lantern.dll, repeat the WINE.md evidence method with a test prefix, and only then consider the game's prefix. Frames stay off the render thread; the plugin only sees lt_poll events for mute and level.",
      "risks": "cpal's WASAPI path under Wine goes through Wine's audio stack, whose latency and device handling vary by distro. A crash in audio code is a game crash, which is the reason the sidecar exists. It may simply not be good enough, and the honest result could be 'no'.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 190
    },
    {
      "mod": "xivmcp",
      "id": "mcp-native-duty-list-rows",
      "title": "Real Rows in the Duty List",
      "pitch": "Replace the overlay with native nodes inside the Duty List itself, so custom objectives and agent posts move, collapse and hide exactly as quests do.",
      "how": "Option (a) in OBJECTIVES.md: node injection into _ToDoList through IAddonLifecycle (PostSetup, PostRequestedUpdate, PostRefresh, PreFinalize), either through KamiToolKit or Dalamud's native overlay service once goatcorp/Dalamud#2874 is merged. It is a drop-in for ObjectiveOverlay and DutyListAnchor; ObjectiveStore, the evaluator and the tools do not change.",
      "risks": "AddonToDoList has no slot for plugin rows and rebuilds its ListPanel itself; a node still linked at finalize (zone change, HUD layout change, unload) crashes the game rather than failing softly. KamiToolKit is a large dependency to keep current with patches. None of it can be tested outside the running game.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 191
    },
    {
      "mod": "xivpiano",
      "id": "piano-listen-together",
      "title": "Listen Together",
      "pitch": "A party or linkshell tunes in to the same station's seeds together, each person on their own Pandora account, with play and pause kept in step.",
      "how": "The code carries the station's seeds (from station.getStation with includeExtendedAttributes, as pianobar asks for them) as music tokens. Each listener's XivPiano makes a station with station.createStation from the first and adds the rest with station.addMusic, then plays it. Play and pause travel as small messages over XivLantern's channels when it is installed, or not at all. Nothing but the code and play/pause state is shared.",
      "risks": "Every listener hears different songs, because Pandora makes a playlist per listener and XivPiano will never pass audio from one player to another (that would be rebroadcasting, which Pandora's terms forbid). So 'together' means the same kind of music, not the same song, and the page must say that honestly. Everyone needs a US Pandora account. It adds a station to each listener's list, which they may not want to keep.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 192
    },
    {
      "mod": "xivwayfinder",
      "id": "way-bead-behind-walls",
      "title": "A Bead That Knows About Walls",
      "pitch": "Draw the bead in the world itself rather than on top of the screen, so a pillar or a companion can stand in front of it the way real things do.",
      "how": "Overlay draws everything on ImGui's foreground list, which has no depth. Two routes to try: spawn a client-side VFX at the bead's world position (the approach Splatoon-style plugins take), or read the game's depth buffer and test the bead's projected point against it before drawing. Either way the bead's position stays computed by Heading.Ahead and Pulse, and the ImGui bead remains the fallback.",
      "risks": "Both routes need hooks or signatures that break on patches, and a VFX left behind on unload or a crash stays in the scene. Reading the depth buffer is renderer-specific and may not work the same under Wine. May cost more frame time than the whole plugin does today.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 193
    },
    {
      "mod": "xivwayfinder",
      "id": "way-timeworn-map",
      "title": "X Marks the Spot",
      "pitch": "Once you have deciphered a timeworn map, point at the dig site it shows.",
      "how": "The TreasureSpot and TreasureHuntRank sheets give the candidate Level rows; which one is yours is only known after deciphering. Research whether the client exposes it in an agent (as the treasure-map UI must know it) before considering a hook. The result becomes an Explicit Target, so the aetheryte hint and vnavmesh work unchanged.",
      "risks": "If the spot is only in a network packet, reading it means a hook, which is fragile and a larger step than anything the plugin does now. Established plugins already fill this niche. Must never extend to anything the player has not deciphered themselves.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 194
    },
    {
      "mod": "almanac",
      "id": "almanac-wasm-tool-sources",
      "title": "Sandboxed WebAssembly Tools",
      "pitch": "Let players add small tools, such as a gil calculator or a rotation lookup, as WebAssembly modules that can reach nothing they are not granted.",
      "how": "The WasmToolSource sketched in ToolSources.cs's doc comment: one module per tool, exporting its ToolDef JSON and call(args-json) -> result-json, run under WASI with no preopens or sockets by default. It only has to implement IToolSource; Engine.NewLoop adds it to the ToolHub list after XivMcp, so XivMcp keeps name priority. Per-module grants shown in SettingsWindow; the runtime is disposed in Engine.Dispose and counted by LiveObjects so the reload leak tests cover it.",
      "risks": "A Wasm runtime such as Wasmtime brings a native library that has to load inside Dalamud on Windows and under Wine, the same class of problem as the SQLite native library, and must unload cleanly on plugin reload. Fuel or time limits are needed so a looping module cannot stall a turn. Sharing modules invites people to run code from strangers; grants must default to nothing.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 195
    },
    {
      "mod": "xivdesktop",
      "id": "desk-clipboard-bridge",
      "title": "One Clipboard for Both Worlds",
      "pitch": "Copy in a host app panel and paste into game chat, or the other way round, through the agent.",
      "how": "The game side is Wine's Windows clipboard, reachable from the plugin through ImGui's clipboard functions. The compositor side needs ghostty-agent to act as a Wayland data-device client in its own compositor. XivDesktop would watch for a change on either side and relay text only, with a size cap, through a new agent method; PaletteCommand.Copy already writes the game side.",
      "risks": "Needs agent work in ghostty-dalamud. Password managers put secrets on the clipboard, so relaying must be opt-in, text only and skip sensitive MIME hints. Two-way sync loops need care. Wine's clipboard is already bridged to the host X11 or Wayland session on some setups, which could produce three clipboards fighting.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 196
    },
    {
      "mod": "xivmcp",
      "id": "mcp-house-aware-terminal-layouts",
      "title": "Terminal Layouts That Remember the House",
      "pitch": "Saved Ghostty layouts tied to a housing plot and room, so apply_terminal_layout puts your panels back on the same wall every time you walk in.",
      "how": "get_terminal_layouts and apply_terminal_layout already exist and answer 'unavailable', because TODO.md notes that '/term pin' has no absolute world-position form. This needs that form in GhosttyDalamud first, then layouts keyed by get_housing_info's ward, plot and room, written through the layout.get/layout.set verbs that SiblingIpcProposals.cs leaves out today.",
      "risks": "The blocking work is in another mod and has to be designed there. Housing interiors share coordinates across plots, so the key must include the house identity. The sibling IPC patches have not been applied or built. Panels have no depth test and show through walls.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 197
    },
    {
      "mod": "ghostty",
      "id": "gpose-terminal-studio",
      "title": "GPose Terminal Studio",
      "pitch": "Terminals stay in group pose as frozen props you can scroll back, move, tilt or attach to a character's hand.",
      "how": "Set DisableGposeUiHide; confirm draw callbacks and ViewProjection follow the GPose camera. Freeze via Session:sleep. Drag/rotate gizmo on world_hit/world_unproject. Hand attach via a GetBone shim forward reading hkaPose and a Lua 'bone' anchor.",
      "risks": "ImGui overlays never get game DoF or lighting and cover other actors. The game's screenshot likely excludes Dalamud's layer. Bone math breaks on ClientStructs changes.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 198
    },
    {
      "mod": "ghostty",
      "id": "kana-on-the-curve",
      "title": "Kana on the Curve",
      "pitch": "IME composition text renders inline at the cursor, even on curved world panels, with a game-styled candidate list.",
      "how": "Committed text and wide cells already work. Shim hooks WM_IME_COMPOSITION/ImmGetCompositionString (or reflects into Dalamud) to forward preedit and cursor. render.nelua draws the underlined overlay in the same draw range so world_transform_vertices curves it. Candidate frame is ImGui; merge a CJK font subset.",
      "risks": "CJK at 40px bloats the atlas; ImWchar is 16-bit so no astral characters. IME through Wine/Proton on Linux is unreliable. WndProc hooks can conflict with Dalamud's IME.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 199
    },
    {
      "mod": "xivarcade",
      "id": "arcade-almanac-names",
      "title": "Ask the Local Model for a Name",
      "pitch": "When cover matching fails, ask your own local model through Almanac for the game's canonical name, and keep the answer only if it really exists in the console's list.",
      "how": "This is the TODO already written in docs/ARCADE.md. The helper writes unmatched names and their console's cached Named_Boxarts list to state.json; the plugin sends each to Almanac.Ask with a narrow prompt; the answer goes back through a new helper verb and art_match accepts it only if it is exactly one entry in that list. XivMcp stays out of it, as the doc says.",
      "risks": "Needs a request and response bridge between the in-game IPC and the host helper, which is why it was left out. Small local models invent names; the exact-list check makes that safe but may leave most games unmatched. Slow on modest GPUs, so it must run in the background and be cancellable.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 200
    },
    {
      "mod": "xivpiano",
      "id": "piano-lyrics",
      "title": "Lyrics, Where Licensed",
      "pitch": "Show the words of the song that is playing, if a licensed source that allows it can be found.",
      "how": "Pandora's partner API that pianobar documents has no lyrics call, and Pandora's own apps show lyrics from a licensed provider through their private web API, which XivPiano does not use. So this would mean a lyrics provider whose terms allow a third-party player to show them, keyed on Track.Title and Track.Artist, shown in a tab in MainWindow.",
      "risks": "Lyrics are licensed separately from the music. Scraping them from lyrics sites, or lifting Pandora's own, would break someone's terms and their licence, which is why this is marked unsafe as it stands. It is listed to see whether people want it enough to look for a proper licence; without one it will not be built.",
      "feasibility": "research",
      "effort": "weeks",
      "topPick": false,
      "value": 6,
      "votes": {
        "want": 2,
        "maybe": 0,
        "skip": 0
      },
      "basis": "confirmed ballot",
      "status": "queued",
      "evidence": null,
      "rank": 201
    },
    {
      "mod": "xivprism",
      "id": "xivprism-spatial-panels",
      "title": "Independently pinned panels",
      "pitch": "Arrange separate panels in space after the peripheral HUD is usable.",
      "how": "Optional tracking adapters and calibration, validated per model.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "research",
      "effort": "large",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 202
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-presets",
      "title": "Campfire, work corner and adventure",
      "pitch": "Switch between a few arrangements that fit the moment.",
      "how": "Versioned preset documents with clear panel selections.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "small",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 203
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-prism-layout",
      "title": "Spread a camp across displays",
      "pitch": "Keep the journal on a tablet and navigation in glasses.",
      "how": "Prism output bindings and per-client fallback rules.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 204
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-screen-layout",
      "title": "Remember screen positions",
      "pitch": "Return panels to their saved sizes and places.",
      "how": "Live window-provider geometry and reversible ownership.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 205
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-world-windows",
      "title": "Unfold panels into the world",
      "pitch": "Arrange a personal semicircle of useful windows.",
      "how": "Optional Ghostty hosting with actual-state reporting.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "large",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 206
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-direct-messages",
      "title": "A compact DM inbox",
      "pitch": "Keep a few useful conversations close while you play.",
      "how": "Supported Slack conversation APIs with authorized user access.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 207
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-mentions",
      "title": "Mentions worth seeing",
      "pitch": "Surface selected mentions without filling the game screen.",
      "how": "Event updates and user-selected notification filters.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 208
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-official-window",
      "title": "Official Slack within reach",
      "pitch": "Start with the Slack client you already trust and use.",
      "how": "Evaluate Desktop and Ghostty hosting of the official app window.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 209
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-threads",
      "title": "Read and reply to threads",
      "pitch": "Keep replies attached to their conversation.",
      "how": "Thread-aware history and explicit composition, respecting API limits.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 210
    },
    {
      "mod": "xivprism",
      "id": "xivprism-android",
      "title": "Android and wearable viewers",
      "pitch": "Use an Android device with or without attached glasses.",
      "how": "A native Android shell, with model-specific presentation adapters.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "large",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 211
    },
    {
      "mod": "xivprism",
      "id": "xivprism-display-profiles",
      "title": "Profiles that follow your glasses",
      "pitch": "Plug in a remembered display and recover its own layout.",
      "how": "Client-side identities, explicit first association and hotplug state.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 212
    },
    {
      "mod": "xivprism",
      "id": "xivprism-standalone",
      "title": "Standalone Prism app",
      "pitch": "Open only the HUD, without launching Moonlight.",
      "how": "One renderer inside native desktop, Android and iPadOS shells.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "large",
      "topPick": false,
      "value": 8,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 213
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-controller",
      "title": "A camp from the couch",
      "pitch": "Choose and dismiss arrangements without fighting game controls.",
      "how": "Explicit focus ownership and accessible keyboard/controller navigation.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 214
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-dispatch",
      "title": "A work corner for Slack",
      "pitch": "Bring a selected Dispatch panel into a saved arrangement.",
      "how": "Panel adapters with no Slack credentials in scene files.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 215
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-export",
      "title": "Share a layout draft",
      "pitch": "Export arrangements without account credentials or private messages.",
      "how": "A versioned geometry-only document with validation on import.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "small",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 216
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-lantern",
      "title": "A camp with your friends",
      "pitch": "Keep a selected Lantern conversation within reach.",
      "how": "An optional panel integration without publishing the scene automatically.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 217
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-missing-providers",
      "title": "A camp that survives missing mods",
      "pitch": "Open the available panels and explain what is missing.",
      "how": "Capability discovery and independent provider errors.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 218
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-music",
      "title": "Music for your arrangement",
      "pitch": "Recall a selected music view or station when you choose a scene.",
      "how": "Optional Piano IPC and explicit, reversible music actions.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 219
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-restore",
      "title": "Put everything back",
      "pitch": "Close a camp and restore the arrangement that came before it.",
      "how": "Owned leases, saved prior state and rollback on partial failure.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "large",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 220
    },
    {
      "mod": "xivcamp",
      "id": "xivcamp-rug",
      "title": "Make the map rug your centre",
      "pitch": "Build an arrangement around Rug and its route view.",
      "how": "An optional Rug provider with explicit placement support.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 221
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-account-lifecycle",
      "title": "Clear account and permission controls",
      "pitch": "See which workspace is connected and disconnect it cleanly.",
      "how": "OAuth lifecycle, revocation, secure credential storage and honest identity display.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "large",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 222
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-controller",
      "title": "A controller-friendly conversation",
      "pitch": "Open, browse and dismiss the panel with clear focus ownership.",
      "how": "Explicit interaction mode, keyboard access and large touch targets.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 223
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-drafts",
      "title": "Drafts that survive closing",
      "pitch": "Finish a message later without losing what you typed.",
      "how": "Local drafts keyed by workspace and conversation, with a visible clear action.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "small",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 224
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-links",
      "title": "Open the original message",
      "pitch": "Hand off complex work to the official client.",
      "how": "Supported permalinks and an explicit open action.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "small",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 225
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-pinned-channels",
      "title": "Only the channels you choose",
      "pitch": "A small useful sidebar instead of every workspace channel.",
      "how": "Explicit channel pinning and per-workspace selection.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "small",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 226
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-prism-panel",
      "title": "Dispatch on your glasses",
      "pitch": "Read a selected conversation on a companion display.",
      "how": "A compact panel contract that never contains connector secrets.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 227
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-quiet",
      "title": "Quiet play sessions",
      "pitch": "Queue notifications until your chosen quiet session ends.",
      "how": "Local notification policy and a clearly visible quiet state.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "small",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 228
    },
    {
      "mod": "xivdispatch",
      "id": "xivdispatch-reactions",
      "title": "Quick deliberate reactions",
      "pitch": "Acknowledge a message without writing a full reply.",
      "how": "Supported reactions API with user identity and explicit input.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 229
    },
    {
      "mod": "xivprism",
      "id": "xivprism-hud-family",
      "title": "HUD-family panels",
      "pitch": "Bring Journal, Character, Wayfinder and Piano to companion displays.",
      "how": "Versioned adapters expose only supported panel information.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "large",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 230
    },
    {
      "mod": "xivprism",
      "id": "xivprism-ipad",
      "title": "iPad companion",
      "pitch": "Use the tablet itself or an available external presentation display.",
      "how": "A native iPadOS shell using supported display scenes and lifecycle handling.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "large",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 231
    },
    {
      "mod": "xivprism",
      "id": "xivprism-mcp-status",
      "title": "MCP presentation diagnostics",
      "pitch": "Let your assistant inspect which viewer actually rendered its layout.",
      "how": "Expose capabilities, source freshness and requested versus actual output state.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 232
    },
    {
      "mod": "xivprism",
      "id": "xivprism-multi-output",
      "title": "Different panels on each output",
      "pitch": "Keep navigation in glasses and a full journal on a tablet.",
      "how": "A shared layout contract with independent outputs and scaling.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 233
    },
    {
      "mod": "xivprism",
      "id": "xivprism-recovery",
      "title": "Unplug and recover",
      "pitch": "Optional game HUD relocation reverses when its viewer disappears.",
      "how": "Lease-owned changes and freshness checks, tested across disconnects.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "high",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 234
    },
    {
      "mod": "xivprism",
      "id": "xivprism-whole-pinning",
      "title": "Use existing glasses pinning",
      "pitch": "Place the entire HUD surface using your device's available pinning.",
      "how": "Treat native pinning separately from independently tracked panels.",
      "risks": "Live provider, platform and hardware behavior must be tested before this feature is advertised as available.",
      "feasibility": "medium",
      "effort": "medium",
      "topPick": false,
      "value": 7,
      "votes": {
        "want": 1,
        "maybe": 0,
        "skip": 0
      },
      "basis": "live Want majority",
      "status": "queued",
      "evidence": null,
      "rank": 235
    }
  ],
  "review": [
    {
      "mod": "xivwayfinder",
      "id": "way-nearest-fate",
      "title": "Towards the FATE",
      "votes": {
        "want": 1,
        "maybe": 1,
        "skip": 0
      },
      "reason": "Outside the confirmed ballot; needs a new acceptance decision."
    }
  ],
  "tracking": {
    "ghostty": {
      "repo": "Spaceghost/ghostty-dalamud",
      "url": "https://github.com/Spaceghost/ghostty-dalamud/issues/75",
      "number": 75
    },
    "xivmcp": {
      "repo": "Spaceghost/xivmcp-dalamud",
      "url": "https://github.com/Spaceghost/xivmcp-dalamud/issues/27",
      "number": 27
    },
    "xivdesktop": {
      "repo": "Spaceghost/xivdesktop-dalamud",
      "url": "https://github.com/Spaceghost/xivdesktop-dalamud/issues/24",
      "number": 24
    },
    "xivarcade": {
      "repo": "Spaceghost/xivarcade-dalamud",
      "url": "https://github.com/Spaceghost/xivarcade-dalamud/issues/11",
      "number": 11
    },
    "xivwayfinder": {
      "repo": "Spaceghost/xivwayfinder-dalamud",
      "url": "https://github.com/Spaceghost/xivwayfinder-dalamud/issues/11",
      "number": 11
    },
    "xivlantern": {
      "repo": "Spaceghost/xivlantern-dalamud",
      "url": "https://github.com/Spaceghost/xivlantern-dalamud/issues/2",
      "number": 2
    },
    "xivpiano": {
      "repo": "Spaceghost/xivpiano-dalamud",
      "url": "https://github.com/Spaceghost/xivpiano-dalamud/issues/9",
      "number": 9
    },
    "almanac": {
      "repo": "Spaceghost/almanac-dalamud",
      "url": "https://github.com/Spaceghost/almanac-dalamud/issues/25",
      "number": 25
    },
    "xivprism": {
      "repo": "Spaceghost/xivprism-dalamud",
      "url": "https://github.com/Spaceghost/xivprism-dalamud/issues/1",
      "number": 1
    },
    "xivdispatch": {
      "repo": "Spaceghost/xivdispatch-dalamud",
      "url": "https://github.com/Spaceghost/xivdispatch-dalamud/issues/1",
      "number": 1
    },
    "xivcamp": {
      "repo": "Spaceghost/xivcamp-dalamud",
      "url": "https://github.com/Spaceghost/xivcamp-dalamud/issues/1",
      "number": 1
    }
  },
  "total": 235,
  "counts": {
    "ghostty": 73,
    "almanac": 20,
    "xivarcade": 17,
    "xivcamp": 12,
    "xivdesktop": 18,
    "xivdispatch": 12,
    "xivlantern": 20,
    "xivmcp": 16,
    "xivpiano": 18,
    "xivprism": 10,
    "xivwayfinder": 19
  }
}
