Skip to content

Latest commit

 

History

History
541 lines (386 loc) · 35.4 KB

File metadata and controls

541 lines (386 loc) · 35.4 KB

TODO

This is the operational queue for open-ended VirtualDJ reference work.

Agents should start here for maintenance, cleanup, documentation, and evidence-pass tasks. Pick the first Ready task unless the user names a different task. Read the task-listed files before running broad repository searches.

Queue Rules

  • Ready: startable now with the listed files and fixtures.
  • Blocked: needs hardware, a live VirtualDJ setup, or a clearer external source.
  • Parking lot: useful later, but not the next best use of time.
  • Record manual VirtualDJ observations in docs/VDJScript Local Test Tracker.md.
  • Promote stable conclusions into the topical docs named by the task.
  • Run just check after documentation, fixture, or status edits.
  • Read first lists are section-scoped: read only the named rows/sections. Use just grep-verb-docs <name> for verb lookups instead of opening docs/VDJScript Verbs.md.
  • docs/VDJScript Reference Consolidation Plan.md and docs/Completeness Roadmap.md are frozen design references. Do not refresh, reorder, or re-scope them; this file is the only active queue.

Ready Tasks

0. Build The Verb Record Store And just Data API

Status: Foundation landed (2026-07-22) — generation + migration remain

The store and its query/edit API exist and are wired into just check. This is the compounding-cost reducer: it replaces the record-in-tracker-then-promote-to-three-docs cycle with one just put-verb, and lets agents query verb state without loading the 6,300-line monolith.

Done in this pass:

  • tools/verbdb.py over the authoritative store docs/vdjscript-verbs.json, fronted by just get-verb / put-verb / find-verbs / next-incomplete-verb / verb-stats. Storage is private behind the API so it can later become one-file-per-verb without retraining agents.
  • Merge-safe bootstrap seeded all 991 records from the index + coverage audit (official names + Needs-Local-Test gap) + tracker status tables. It correctly finds the 19-name gap (17 hardware-blocked → skipped by next-incomplete), leaving dualdeckmode_decks and system as the 2 active items, and auto-detected 7 tracker Pass rows.
  • verbdb.py check (schema, alias resolution, index coverage, count freshness) is in just check. Entrypoints (AGENTS.md, INDEX.yml, docs/README.md, tools/README.md) route verb lookups and result-recording to the flat just get-verb / find-verbs / put-verb commands.

Reports are queries, not files (2026-07-22):

  • just find-verbs filters on --surface, --section, --tier, --status, --kind, --needs-test, with --format=json for structured output and --limit. A category listing is just an unfiltered query, so no derived Markdown is written to disk — nothing can drift, and there is no staleness gate to maintain. An earlier pass generated docs/VDJScript/generated/*.md and was reverted for exactly this reason.
  • Rule for future work: do not add a generator that writes a Markdown copy of store data. If a view is wanted, add a query or a flag. Building reader-facing documentation is a later phase, driven by findings — not something to design for now.

Remaining:

  • Add richer record fields as needed by contracts (forms, platforms, deck_scope); put currently covers the scalar/list fields, nested contract detail is hand-edited in the JSON.
  • Grow the query layer where a real question is awkward to ask (e.g. verbs by evidence source, or by presence of a local-test note).
  • The monolith still holds the authored prose. Retiring it follows the frozen plan's phased, one-family-at-a-time migration; do not delete hand-authored docs ahead of that.

Effect catalog is queryable (2026-07-22): tools/fxdb.py / just get-fx / find-fx / fx-stats answers slider/button questions straight from the sweep artifact, gated by fxdb.py check in just check. No Markdown copy — same rule as the verb store.

Tasks 1-4 are one FX cluster: they share the same VirtualDJ session and the same deck-FX context. Batch them into one local-test session where possible. Preferred readback channel: the HTTP control interface (just vdj-query), which returns exact strings and makes the sweeps scriptable — the older name=-interpolation pad technique (proven on v2026-m b9482) is now needed only for pad/skin-surface-specific checks.

0b. Topic Search Across Every Corpus

Status: First cut landed (2026-07-26) — coverage tagging remains

The done-when is met: just topic <term> (tools/topic.py) answers a topic question with matching verbs, effects, and XML elements and the real example files that use them (grep-verified, ranked by coverage), plus topical docs and local-test quirks. It is pure aggregation over the already-gated stores — no artifact, no hand-tagging — deriving everything from verb section, inventory families, and word-boundary grep. Wired into AGENTS.md, INDEX.yml, and just check (cross-store smoke test).

Remaining — the metadata layer, which is the part that needs real tagging:

  • Topic reach for name-opaque items. An item is only found under a topic if the topic appears in its name, section, or a grep of it. That misses families whose topic is not in the element name — searching waveform does not surface rhythmzone, scratchwave, blockwave, beattunnel (only the doc pointer saves it). These need an explicit topic tag. This is the "rich metadata to each searchable item" idea, and it is good mechanical subagent work: add a topics: [...] field to store records and an element→topics map, then have topic.py consult it alongside the derived matches.
  • Multi-word terms are treated as one string (color fxcolorfx); a synonym/alias map would fold those together.
  • Keep it a query — no generated topic pages. Same rule as everywhere else.

Read first:

1. Complete The Per-Effect FX Introspection Sweep

Status: Structural sweep COMPLETE (2026-07-22) — only rendering behavior is left

tools/sweep_fx_introspection.py captured counts, short+full labels, normalized defaults, live value text, and length/beats flags for all 119 installed effects into tests/fx-introspection-dump.json, plus the enabled cycle for all three targets. Query it with just get-fx <effect> / just find-fx [--category=deck_fx|video_fx|transition] [--has-length] / just fx-stats — do not read the dump and do not hand-transcribe it.

What the sweep settled:

  • Introspection is read-only. Every get_effect_* helper accepts an effect name where the docs show a slot number (get_effect_slider_count 'Echo', get_effect_slider_default 'Echo' 3), returning the same values as the slot form for all 119 title-resolvable effects with no effect_select and no state change. This is the cheap way to ask about an effect that is not loaded.
  • get_effect_title '<name>' returns '<Canonical> - Deck N' or '', so it resolves a name to its canonical spelling and probes existence. Case-insensitive, not space-insensitive. Blind spot: '' for Stems and Vocals, which select and introspect fine through a slot — so a title miss must be confirmed by selecting before the name is called unknown.
  • Audio-vs-video: cycle membership, not loadability. All three selectors accept any installed effect name (video_fx_select 'Echo' really does set the video slot to Echo), so what a target accepts discriminates nothing. The three +1 cycles are disjoint and are the app's own category assignment: 63 deck FX, 17 video FX, 35 transitions. Each is the enabled/favorites subset, so an installed effect in no cycle (Lottery, Sweep, Title, Vocals) is category-unknown, not uncategorised.
  • Brake and Shader resolved. Brake is not a selector name on this build at all — a docs-catalog error; the real ones are BrakeStart, VinylBrake, Beat Brake. Shader is an alias for Visuals, which loads into a deck slot perfectly well; the original sweep only ever asked for it by the wrong name. BeatGrid is likewise a spacing error for Beat Grid. Nothing here was ever "video-only".
  • *_skip_length re-indexes, it does not blank. Index i is the i-th slider with the length slider removed, so the last index is always empty. Verified on all 47 length-bearing effects; the length slider is not always index 2 and not always labelled LEN.

Remaining (rendering behavior, needs video output — not introspection):

  • video_fx_slider, video_fx_clear, video_transition_slider, and deck master scoping: what they actually render.
  • Whether the 4 category-unknown effects land in a target's list when enabled in the FX list editor.
  • Promotion stays deferred to TODO task 0: the data is queried from the artifact, not copied into Effects Engines.md. Do not hand-transcribe the dump.

Start here:

Read first:

Record results in:

Done when:

  • The remaining video-rendering verbs get at least one recorded pass each.
  • Generic FX control guidance distinguishes observed behavior from inference.

2. Characterize FX Bank Save And Load

Status: DONE (2026-07-26, HTTP). A bank is a rack of effect SELECTIONS for slots 1-6 — not active state, not slider values, and global across decks. effect_bank_load returns true/false as a bank-populated probe. Recorded in the tracker and on effect_bank_save/effect_bank_load (just get-verb effect_bank_save).

Start here:

Read first:

Record results in:

Promote to:

Done when:

  • Restored effect names, active states, slider values, and deck scope are recorded.

3. Separate Release FX From Normal Slot FX

Status: PARTIAL (2026-07-26, HTTP). Confirmed the release-FX path is separate from deck slots 1-6 (is_releasefx never flips from loading effects into numbered slots); the release sliders are accepted but inert without an armed release FX, which needs a momentary control HTTP can't drive. Remaining: arm a release FX on a pad/mapper surface and characterize activation. Recorded in the tracker and verb store.

Start here:

Read first:

Record results in:

Promote to:

Done when:

  • effect_releaseslider* and is_releasefx behavior is described separately from normal deck FX controls.

4. Keep BeatGrid effect_command Plugin-Specific

Status: DONE (2026-07-26, HTTP). Confirmed plugin-instance-scoped (targets the BeatGrid slot), with a bare form and an unquoted-slot-number form; get/set/cur are BeatGrid's own vocabulary. Recorded as BeatGrid-specific, not generic. See just get-verb effect_command.

Start here:

Read first:

Record results in:

Promote to:

Done when:

  • Confirmed effect_command examples are documented as BeatGrid-specific rather than generic plugin control advice.

5. Author And Load-Test A Minimal Custom Device Definition

Status: MAPPER FIRING DONE (2026-07-27, DDJ-GRV6 hardware) — device-definition schema still open. HTTP-verified on real hardware that the mapper <map value action> schema binds and fires (ONINIT on load, PLAY_PAUSE on press), plus three gotchas: control names must match the device definition exactly (wrong name fails silently), loading a mapping resets $ globals, and editing an active mapper file needs a full restart (re-select does not reload). See the tracker's "Mapper Firing" section and docs/Mapper XML.md. Factory-mapping export (Factory default -> Save) was tried as a shortcut to the device definition: it yields the factory <mapper> (control names + canonical actions, 293 bindings, lints clean) but NOT the <device> definition, so it does not unblock this. STILL OPEN: the custom <device> definition schema is untested because the DDJ-GRV6 is factory-recognized — needs unrecognized hardware or a virtual MIDI port + injection to exercise a custom device definition.

The mapper reference's device-definition schema is official-doc-derived but never load-tested locally. A SIMPLE_MIDI device context already exists in the local install's Mappers folder. Mappers are one of the repo's named coverage cliffs, so this is the highest-value task outside the FX cluster.

Start here:

Done when:

  • A minimal <device type="MIDI"> XML placed in the VirtualDJ Devices/ folder is detected by the app, and a paired mapper's <map> bindings fire.
  • Results (including failures) are recorded in docs/VDJScript Local Test Tracker.md and promoted into Mapper XML.md source labels (Local test).
  • RESOLVED 2026-07-27 for two of the three mapper-lint warnings (and note these were never factory-sourced: all three came from personal local mappings, mis-graded as factory by the now-corrected author-attribute rule (the tag does not track authorship) in examples/Mappers/README.md): browser_filter and browser_search are not verbs on this build — no ACTION_ symbol and no bare string anywhere in the executable, no Button Editor autocomplete, E_FAIL over HTTP. The lint warnings are correct and the mapper lines using them do nothing; clear_search is the real verb. none remains unresolved (no ACTION_ symbol and no autocomplete, but "none" is an English word so its presence in the string table proves nothing); its only observed use is as a do-nothing LED placeholder. See the disproof method in docs/Undocumented VDJScript Candidates.md.

6. Continue Hidden Button Editor Candidate Probes

Status: Ready — reframed 2026-07-29: these are no longer "candidates". All 37 hidden names are proven real by verb-table membership (flags == 256), every one now has a verb-store record, and 34/37 have HTTP-proven kind. What this task probes is behavior only.

Start here:

Read first:

Record results in:

Promote to:

Done when:

  • Candidate behavior is recorded as pass, partial, failed, or still discovery-only.
  • Catalog-only names stay out of ordinary recommendations unless behavior proof supports promotion.

7. Repeat dualdeckmode_decks In A Better Context

Status: Ready, but low expected yield until a concrete context is identified

The first pad-context run (v2026-m b9336) recorded dualdeckmode toggling on while current and deck-scoped dualdeckmode_decks readbacks stayed false on both decks. The promotion condition is a visible dual-deck pair or controller context (deck pairs 1/3 or 2/4), which realistically means a 4-deck skin setup or a controller. Do not repeat the same pad-context probe; identify the better context first, or treat this as semi-blocked.

Start here:

Read first:

Record results in:

Promote to:

Done when:

  • A build, deck/controller context, observed result, and follow-up decision are recorded.
  • The official local-test status is updated only if the repeat confirms behavior well enough for promotion.

8. Characterize The VirtualDJ Remote App Wire Protocol

Status: DONE (2026-07-27) — transport, wire format, subscriptions, and actions are all verified in both directions; only minor open questions remain (see end of this task)

Settled with a live session (socket watcher + dns-sd + per-connection nettop deltas; recorded in the tracker, docs/HTTP Control Interface.md, and docs/Application Internals.md Remote Skins):

  • Remote does not use the Network Control HTTP channel; port 80 saw no Remote traffic.
  • Discovery is inverted from the obvious guess: the phone advertises Bonjour type _vdjremote8._tcp (SRV → phone, port 4243 observed) and listens; VirtualDJ connects out to the phone as the TCP client, one persistent connection.
  • Semantics are event-driven push: idle seconds carry 0 bytes on that connection; a deck load pushed ~249 KiB desktop→phone in one second with no inbound request; unload ~1.4 KiB; otherwise only sub-KB keepalives.

Wire format also DONE (2026-07-27) — see docs/Remote Protocol.md. Framing is 8JDV + u32 total length + u16 type; the device opens with subscription frames carrying ordinary VDJScript queries by id, and VirtualDJ pushes typed values (val float32 / txt / fail) plus browser folder XML, settings, and selected-folder state. Replaying a captured opener is enough to hold a session — no pairing token. Capture tool: python3 tools/vdjremote_dial.py <device-ip>; reference capture at tests/vdjremote-opener.bin.

Subscriptions also DONE (2026-07-27): the vocabulary is all of VDJScript, not a fixed schema. Verified by substituting synthetic SUBSCRIBE frames into a replayed opener — get_version, get_effect_name 1, deck 3 get_bpm, and a full ternary all resolved, and push-on-change was measured (a load pushed title/artist/BPM/path within the same second; get_position streamed at 33-34 Hz while playing, silent when paused). KIND is a hint, not a request; fail means "no value now", not "bad query". Tool: python3 tools/vdjremote_subscribe.py tests/vdjremote-opener.bin 'left:get_title' 'get_clock' paired with a dns-sd -R advert.

Remaining:

  • Mid-session subscribe/unsubscribe is untested; only opening-burst registration has been exercised. A client that switches views needs it.
  • Map or bypass the 0x02 control id space. Only four ids are known, all from one device skin (0xc6 play, 0xc7 cue, 0x41 crossfader, 0x36 volume). Whether the space is global or skin-defined is open — but 0x31 may make it moot for third-party clients.
  • Mid-session subscribe/unsubscribe is untested — only opening-burst registration has been exercised. A client that switches views needs it.
  • Undecoded types: device→desktop 0x09, 0x0c, 0x27, 0x29, 0x34; desktop→device 0x2b, 0x3b. Sessions work without understanding them (replay reproduces them), so this is lower priority.
  • Waveform data has not been located in any frame; check inside the 0x25 ZIP payloads.

Record results in:

Promote to:

Done when:

  • Action frames are catalogued with example payloads, and the subscription vocabulary is characterized as either "any VDJScript query" or a documented subset.

9. Map Verbs To Button Editor Categories

Status: DONE (2026-07-27) — verb set, aliases, hidden flag, and categories all extracted

The exact verb set is settled: tools/extract_verb_table.py extracts VirtualDJ's own verb table (1,028 sorted 16-byte records {const char *name; uint32 id; uint32 flags} at 0x10402d020), covering 1,007/1,007 HTTP-proven names. Existence and non-existence are now a membership test (just verb-table <name>), aliases are read off shared ids, and flags == 256 marks the 37 Button-Editor-hidden names. 61 alias groups are written into the verb store.

Categories are solved too: a const char *[38] name array after the verb table, plus a non-decreasing uint8[956] in __TEXT,__const giving the category per verb indexed by id + 1. Both located structurally, no pinned addresses. Confirmed by the live Button Editor list (read off screen, flow..video with defines skipped), and reproducing docs/Button Editor Taxonomy.md's per-category counts exactly — all 37 categories, total 1,028. See docs/Undocumented VDJScript Candidates.md §Categories.

Note (2026-07-29) that the count agreement is a reproduction, not corroboration: extract_vdjscript_taxonomy.py reads the same three structures, only by pinned address instead of by anchor. Rule 1c2 in docs/Evidence Standards.md now says so. The live Button Editor remains the only independent check.

Four-verb residue: RESOLVED as doc error. mute, silent_cue, stems_split, and loaded were listed under different categories in the taxonomy doc's example column than the mapping gives. That column is hand-written, not extraction output: the extractor emits visible_examples[:8] over an alphabetically sorted table, yet every doc row has exactly 4 entries and four rows are not even alphabetical (play, pause, stop, silent_cue; mic, linein, aux_volume, mic_eq_high; leftdeck, rightdeck, masterdeck, pfl; beatjump, clone_deck, dualdeckmode, mute). The counts column is extraction output, so the two columns have different pedigree and only the counts carry weight. Mapping stands; the doc's example column is corrected in place.

9b. Remaining Verb-Name Structure Notes

Status: DONE (2026-07-29) — both "done when" conditions are met: every HTTP-proven name is accounted for by a named structure (the verb table covers 1,007/1,007), and every structure-found name absent from the store has been added (the 35 hidden verbs, 2026-07-29). Kept because the corroborating sources are still wired.

Three structures are extracted so far (tools/extract_binary_verbs.py, 1,019 names): 954 ACTION_ implementation classes, 812 language-catalog entries, and the parser's 967-entry alphabetically sorted name table. Their union covers 998 of the 1,007 names the HTTP sweep proved real.

Four names prove at least one more structure exists: browser, config, preview, and volume are real verbs, are ≥4 characters (so strings sees them), and appear in none of the three sources. (jog, no, off, on, yes are also missing but are simply under the strings length floor — use strings -n 2 for those, and note a low minimum breaks the sorted-run detection, so probe them separately.)

Why this is worth doing: the app must match input against a complete set of names, so the sets exist in the binary. Recovering all of them yields the exact verb set — including any undiscovered verbs — which is the strongest possible outcome for this topic, and it lets the disproof drop its conservative string leg (see docs/Evidence Standards.md rule 1a).

Start here:

  • Locate browser / config / preview / volume occurrences and inspect their neighbourhoods for table structure, as was done to find the sorted name table.
  • The sorted table runs action_deckzoom_vertical and omits load, loop, cue, hot_cue, nothing — so it is a subset dispatcher, and whatever holds those five is another candidate structure worth characterizing even though the union already covers them.
  • Both older extractors are stale on this build (extract_vdjscript_symbols.py returns 0; extract_vdjscript_taxonomy.py is address-pinned) — re-anchoring the taxonomy tables would recover the Button Editor's own category structure, which is a likely home for the strays.

Done when:

  • Every HTTP-proven name is accounted for by a named structure, or the residue is explained.
  • Any name found in a structure but absent from the store is swept (just verb-probe) and recorded as a candidate.

10. Discover The Full Function Contract Per Verb

Status: Ready (2026-07-29) — the ratified priority now that existence, aliases, hidden flag, and categories are settled. Goal: for every verb, the complete calling contract — query return type, accepted argument forms, and undocumented overloads — established at Tier 1 where possible and recorded as structured per-verb data, not prose.

Contract fields to establish per verb:

  • Return type in query position: HTTP leg DONE (2026-07-30)tools/sweep_return_types.py sampled all 652 query verbs in three read-only contexts against live VirtualDJ: 623 typed (334 bool / 145 int / 68 float / 4 percent / 72 text), 0 capability conflicts vs the structural matrix, gated in just check, queried with just verb-return-type <name>. It also refuted the first slot labeling: slot 3 is a generic variant query, not onQueryBool — structure claims capability, observation claims type. Remaining sub-legs: the 29 all-empty verbs need richer contexts, and the Remote-protocol typed sweep (val float32 vs txt VALUE frames) would confirm wire-level types independently of HTTP text rendering.
  • Argument forms — binary-first (reprioritized 2026-07-30, partially DONE). The E_INVALIDARG method fingerprint landed in the contracts artifact: 436 verbs demand an argument somewhere, 114/116 agreement with the sweep's needs-args kind, and 301 bare-answering query verbs flagged as taking OPTIONAL arguments — the undocumented-overload queue (summary.optional_arg_queries), invisible to the bare sweep. method_strings recovers per-method keywords (loadedopposite, get_timeshort). Remaining streams for the forms and types:
    1. Argument keywords — DONE (2026-07-30); types — closed as not-extractable. __stubs are now named via DYSYMTAB's indirect symbol table (this build is classic LC_DYLD_INFO_ONLY, not chained fixups), so methods that compare an argument against literals are detectable: 259 verbs carry keyword_candidates, recovering undocumented enums (get_bpm absolute|ghost, browser_window sidelist|karaoke|…, loaded opposite, get_time short, sampler_group_volume touchrelative|locked). Argument types were attempted through the same route and do not survive: param access is inlined, and a verb's library calls describe what it does with an argument, not how it fetches one (_strcasecmp appears in numeric-arg verbs). Types are a Tier-1 job.
    2. HTTP probe in query position (Tier 1): start with the 301 optional-arg queries and the recovered keywords. Method note (2026-07-30, probed live): optional-arg verbs silently ignore unknown arguments (loaded boguswordyes), so error codes cannot confirm a form — probes must compare VALUES across forms in a state where they would differ (e.g. loaded opposite with exactly one deck loaded). get_time demonstrated the pattern: bare returns one mode, any argument switches to another. Query-position probing is side-effect-free; execute-position confirmation only for whitelisted safe verbs, with independent readback (rule 4), never system/file/database verbs.
    3. Shipped-XML corpus mining + appendix parsing (Tier 2): demoted to corroboration of actually-used forms — the binary and the probes discover; the corpus confirms parser-accepted usage.
  • Capability matrix: DONE (2026-07-29) — the vtable-override extraction worked on the first attempt. tools/extract_action_contracts.py walks the surviving RTTI (name → typeinfo → vtable) and emits tests/action-contracts.json: 955 classes ↔ 955 ids as a checked bijection, capability matrix (execute / bool / number / text query) with slot meanings calibrated from HTTP-proven verbs, family taxonomy from the class hierarchy (100 toggles, 56 sliders), and context mixins. Sweep agreement 652/652 (query) and 181/186 (action-only); the 5 outliers surfaced a lead — slot 7 is probably the options-menu provider (browser_export, *_options override only it). Gated in just check; query with just verb-contract <name>. See the Candidates doc §Contract structure.

10a. THE PRIMARY REMAINING APPROACH — a read-only introspection plugin

Status: Ready, and the main plan for the rest of task 10. Everything above was extracted from the binary or observed through HTTP; both are now at their limits for the questions that remain. The C++ plugin interface is the instrument that clears them, and it is documented in docs/Plugin SDK.md.

Why it is the right instrument, not just another one:

virtual HRESULT SendCommand(const char *command)=0;                            // execute
virtual HRESULT GetInfo(const char *command, double *result)=0;                // numeric query
virtual HRESULT GetStringInfo(const char *command, void *result, int size)=0;  // text query
  • Native types, no flattening. HTTP renders everything to text, which is why return types had to be recovered by observation and why master_beat_num shows float bits as an integer. A plugin reads the double directly — and calling GetInfo on it settles in one call whether that defect is in the core or in HTTP's rendering path.
  • A definitive type-path map. Calling both GetInfo and GetStringInfo on each of the 955 verbs, and reading the raw HRESULTs, says exactly which channel each verb answers on — no inference from rendered strings.
  • Throughput. The 301-verb optional-argument queue and the 259 keyword sets need hundreds of probes each; in-process that is a loop, over HTTP it is hundreds of round-trips.
  • Different prerequisites. No Network Control plugin, no Pro license — a fifth Tier-1 channel, which docs/Evidence Standards.md already lists as planned and explicitly forbids claiming anything from until it exists.

Plan:

  1. Build the minimal plugin read-only: GetInfo/GetStringInfo sweeps only, no SendCommand, so loading it cannot change state. Execute-position testing comes later behind an explicit switch.
  2. Emit the same artifact shape as the existing sweeps (tests/…json + a just query) so it joins just get-verb for free.
  3. First three experiments: master_beat_num via GetInfo; the both-channels type map; then the optional-argument queue, remembering that unknown arguments are silently ignored (rule from 2026-07-30), so probes must compare values in prepared state, never error codes.

Prerequisites and constraints:

  • SDK headers are not vendored — no license grant exists. Fetch to vendor/ (gitignored); see the .gitignore entry and docs/Plugin SDK.md.
  • Xcode build, macOS arm64. The community examples build under Xcode 14.3 / macOS 13.3.
  • Open question to resolve while building: the shipped beatport16_vdj bundle exports no DllGetClassObject, so a second loading path may exist. A built test plugin settles it.
  • Also worth probing from inside: whether an internal verb-registration path exists, which is the one scope limit on Evidence Standards rule 1b.

Storage: DONE (2026-07-30)just get-verb <name> joins the store record with the verb table, the structural contract, the HTTP existence probe, and the observed return type at read time (--raw for the bare record). Artifacts stay authoritative; nothing is copied into the store, so a re-extraction is picked up without a migration.

Done when:

  • Every query verb has an observed return type with the observing channel recorded.
  • Every verb has an arg-forms record: observed forms, rejected forms, or no-args.
  • Overloads (same verb, distinct arg shapes with distinct behavior) are recorded as such.
  • The store's per-verb record surfaces all of it through just get-verb.

Blocked Or Hardware-Gated

  • Controller display helpers: controllerscreen_deck, controller_battery.
  • Gemini display helper: gemini_waveform_zoomlevel.
  • Phase helpers: phase_movement, phase_position, phase_active.
  • Numark V7 helper: v7_status.
  • Pioneer RZX helpers: rzx_touch, rzx_touch_x, rzx_touch_y.
  • DJC-family helpers: djc_shift, djc_button, djc_button_popup, djc_button_slider, djc_button_select, djc_panel.
  • Denon platter/display helper: denon_platter.

Parking Lot

  • HTML reference export for humans (requested 2026-07-29, deliberately deferred until the contract data exists): generate a static, browsable HTML reference from the verb store + verb table + contracts artifact — one page per verb plus category/alias indexes. Generation only, from the JSON stores (never hand-written HTML copies, same rule as Markdown); becomes worthwhile once task 10 gives the pages real content beyond names.
  • system: revisit only if an official example, bundled-resource context, or clearly harmless parameter appears.
  • Skin visual type canaries: do after the current no-hardware VDJScript evidence queue unless a skin-specific question makes it urgent.