Open Meet Exchange

Version 1.0. A minor version only adds; only a major breaks. Comments to [email protected].

OMX Integration Prompts

Three prompts, one per stage. Paste the one for your product into your coding assistant. Each prompt lists the raw files of version 1.0 to read, pinned under /omx/1.0/ so they never change, then has the assistant inspect your codebase, propose a mapping and stop for your review, build, test, and run that stage’s conformance tests. /omx/llms.txt lists every raw file.

A product that takes entries and also shows results runs Prompt 1, then Prompt 2, then adds a publish array naming its own endpoint (see “Registration that wants results back” in the specification, and conformance group B).

Treat the output as a first draft. Review the proposed mapping against your data, and check the conformance results by hand.


Prompt 1 — Hand off entries (Registration)

Use this if you hold entries and want to hand meets to Meet Operations: the timing or meet-management software that runs the meet.

I want to add Open Meet Exchange (OMX) support to our product as "Registration": we hold a meet's
entries, and we want to hand the meet to Meet Operations (the timing software) with one URL.

Read these first, and treat them as the authority over anything you remember about meet formats:
- Spec:     https://2timer.app/omx/1.0/spec.md
- Schema:   https://2timer.app/omx/1.0/schemas/registration-document-v1.schema.json (strict: the one we validate with)
- Examples: https://2timer.app/omx/1.0/examples.md (links to one JSON document per sport)
- Tests:    https://2timer.app/omx/1.0/conformance.md
- Mapping:  https://2timer.app/omx/1.0/mapping.md (if we already read or write Hy-Tek, RunSignup, or Athletic.net)
- Checker:  https://2timer.app/omx/1.0/tools/omx-check.mjs (no dependencies; run it with node)
- Types:    https://2timer.app/omx/1.0/types/omx.d.ts or https://2timer.app/omx/1.0/types/omx.py, if we use TypeScript or Python

Our implementation is against OMX 1.0. Cite those pinned URLs in code comments, not the
unversioned ones.

Before writing any code, inspect our codebase and tell me:
1. Which of our tables or models hold meets, events, teams, athletes, and entries.
2. What our primary keys look like, and whether they are stable for the life of a meet. If we
   regenerate ids anywhere, say so: that breaks OMX and we need to fix it first.
3. How we store seed marks: units, precision, and whether imperial field marks are kept as inches
   or converted.
4. How a scratch is represented. If we delete the row rather than flag it, say so.
5. Which sport(s) we actually run: xc, tf-indoor, tf-outdoor, run.
6. How we name our events, so you can map each to the spec's event codes for that sport.
7. Whether an athlete can be entered in one event twice, or a team can enter two relays in one
   event without letters. Either breaks the results join and must be fixed first.

Then propose a field-by-field mapping from our model to the OMX schema, and stop for my review
before implementing. Flag anything we do not have and would need to invent.

Then build:
- A serializer that produces one OMX registration document from a meet id.
- An HTTPS endpoint serving it as Content-Type: application/json at a per-meet, revocable URL. The
  secret in the URL needs at least 128 bits from a cryptographically secure generator (32 base62
  characters is fine) and is redacted from our logs. A revoked or expired URL answers 410, and an
  unknown one 404. Send an ETag and answer If-None-Match with 304: the timer may fetch the link
  more than once.
- Cross-origin headers on that endpoint, because some Meet Operations products run in a browser:
  answer OPTIONS, send Access-Control-Allow-Origin: *, expose ETag, and allow If-None-Match. Never
  rely on cookies. Give out the URL that answers directly: no redirect, such as a bare domain to
  www., because a browser cannot follow one without CORS headers.
- The same document as a file download named <meet>.omx.json, for timers with no connection.
- UI for the meet director to see the link, copy it, and revoke it.

Rules that are easy to get wrong:
- The document says what it is: "omx": { "version": "1.0", "kind": "registration",
  "generator": { "id": <our system id>, "name": <our product name>, "version": <our version,
  optional> } }. Nothing else goes in "omx".
- Ids are opaque strings we choose and NEVER change for the life of the meet. omx.generator.id is
  our system id (any name we pick once and never change, in lowercase letters, digits, and _, at
  most 32 characters; nobody assigns it), the same in every document. Results come back with our meet, event, team, and athlete ids in external_ids under
  that system id; that is how we find our records. Ids we hold for other systems, such as an
  athlete's Athletic.net id, go in external_ids under their system id (anet); never list our own.
  Entries carry no external_ids at all.
- An entry is found by athlete + event, so one athlete is entered at most once in an event. Every
  relay entry carries relay.letter (A for a team's first squad), and team + letter + event names one
  relay entry.
- A scratch is an entry with status "scr", never a missing entry.
- Entry statuses are only "e" (entered, confirmed or not), "scr" (scratched), and "wait" (wait list).
  There is no Pending: send an unconfirmed entrant as "e" or leave them out. Checked in and every
  later status are set at the meet.
- We write who entered, nothing about how the meet runs: no meet.origin, meet.track, rounds,
  markers, heats, lanes, results, places, points, splits, or team scores, and no bib on an entry.
  Send each entry's seed; Meet Operations draws the heats.
- A bib, when we assign one, is the athlete's: athletes[].bib. A runner with two bibs in one meet
  (a 5K on Saturday and a half on Sunday) is two athletes.
- Send marks in the unit we measured. Do not convert inches to millimetres; it is lossy.
- Every event has a "code" the spec defines for the meet's sport, and the code is the whole event:
  a race in metres or miles (100, 5000, 1MILE, 3.1MILE), HMAR, MAR, hurdles (110H), steeplechase
  (3000S), race walk (5000W), a relay (4X400, 1600R), a field event (LJ, TJ, HJ, PV, SP, DT, ...),
  or DEC, HEP, PENT. A race's code is the distance it was advertised at, and each distance has one
  code: a half marathon is always HMAR, never 21097 or 13.1MILE. No distance field. Never write a
  code of our own: an event with no code for its sport is left out, and we tell our users which.
- An athlete in a multi-event (DEC, HEP, PENT, for any gender) has ONE entry, on the multi-event,
  with any seed in pt, and we never send the component events themselves: Meet Operations builds
  them.
- Every other value, such as a sport, is one the format defines. Never write a value of our own.
- Gender: an event open to every gender, including a mixed relay, is "C". Leave an athlete's gender
  out when they did not state one; never guess it.
- An adaptive event (wheelchair, seated, guided) keeps its ordinary code and carries
  "adaptive": {"mode": ...}.
- If we assign start waves, list them in "waves" (offset_ms for a later wave) and point each entry
  at one with wave_id. Award groups go in "groups"; an entry's group_id says which group we placed
  it in when age and gender alone do not decide it.
- Send each athlete's age as we compute it for this meet, or their birth_date, never both.
- Names are published as we send them. Where a name must not be published in full (a minor, an
  opt-out), send it the way it may appear, for example a last initial as the last name.
- Contact details (email, phone), a chip number, and notes are for the timer on meet day; Meet
  Operations never passes them on.
- Any extra field of our own may carry our product's name as a prefix (ourname:field_name), so it
  never clashes with a field the format adds later.
- To move an entry to another event, keep its id and change its event_id. Never delete and re-add.

Do NOT add a publish array yet: I will decide separately whether we want results handed back.
Getting entries out is complete and conformant on its own.

If we later want results back, we will read a results document: the same meet document with
"kind": "results", one entry per athlete or relay per round. Design the serializer's types so a
reader can share them.

Add a test that serializes a realistic meet and checks it two ways: validates it against
registration-document-v1 with a real draft-07 validator (ajv is fine), and runs omx-check.mjs on it.
Both must report zero errors. Also check that the fixtures c3-duplicate-ids,
c4-unresolved-reference, c108-entered-twice, and c13-registration-writes-results from
https://2timer.app/omx/1.0/fixtures/index.json report errors in the same check, so we know it can
fail.

Finish by running conformance group A (OMX-C1 to OMX-C15, OMX-C88, OMX-C100, OMX-C103, OMX-C106,
OMX-C107, and OMX-C108) and showing me the results, with a validator run against the real schema,
not a hand-written check.

Prompt 2 — Publish results (Publisher)

Use this if you want results handed to you. It does not need Prompt 1: a timer may hand you a meet they built themselves, with no Registration anywhere.

I want to receive meets over Open Meet Exchange (OMX) as a "Publisher": Meet Operations (the
timing software) POSTs us results documents, and we store and display them.

Read these first:
- Spec:   https://2timer.app/omx/1.0/spec.md
- Schema: https://2timer.app/omx/1.0/schemas/meet-document-v1.schema.json (lenient: what a reader validates with)
- Results documents: https://2timer.app/omx/1.0/examples/xc-invitational-returned.json,
  https://2timer.app/omx/1.0/examples/tf-outdoor-dual-returned.json,
  https://2timer.app/omx/1.0/examples/tf-indoor-open-returned.json, and
  https://2timer.app/omx/1.0/examples/road-5k-returned.json
- Tests:  https://2timer.app/omx/1.0/conformance.md
- Fixtures: https://2timer.app/omx/1.0/fixtures/index.json (crafted documents for the tests, each with the outcome
  we must produce)
- Types:  https://2timer.app/omx/1.0/types/omx.d.ts or https://2timer.app/omx/1.0/types/omx.py, if we use TypeScript or Python

Our implementation is against OMX 1.0, and accepts any 1.x version.

What arrives is a results document: "omx": { "version": "1.0", "kind": "results", "update":
"full" or "partial", "updated_at": ..., "generator": {...} }, and a meet with one entry per athlete
or relay per round. There is no separate format for live results: a "partial" document is the same
shape carrying only some rounds.

First inspect our codebase and tell me where a meet would live, and whether we already have models
for meets, events, rounds, teams, athletes, and entries or need them.

Then build one endpoint that:
- Answers cross-origin requests, because some Meet Operations products run in a browser: OPTIONS
  allowing Authorization, Content-Type, Content-Encoding, and Content-Digest,
  Access-Control-Allow-Origin: *, and Retry-After exposed. The URL we give out answers directly,
  never through a redirect.
- Authenticates a bearer token we issued with at least 128 bits of randomness, compared in constant
  time and never logged. 200/204 to accept, 401 for a token we do not recognise, 410 for one we have
  retired (optionally with a body of {"reason": "revoked"} or "expired"). We decide how long a token
  works, and keep it working long enough for corrections after the meet.
- Accepts a body sent uncompressed or with Content-Encoding: gzip, of at least 64 MB uncompressed.
- Reads omx.version FIRST. A version we do not implement gets 422 with a message naming the
  version offered and the version we accept ("1.0", which reads any 1.x), never a schema error, and
  the body lists {"accepts": ["1.0"]} so Meet Operations knows what we read.
- Reads omx.kind next. Anything but "results" (missing, unknown, or "registration") gets 422 with
  errors naming /omx/kind. Never work out a document's kind from its fields.
- Reads omx.update. "full" and "partial" are the only values; anything else gets 422 and changes
  nothing. Never read an unknown update as "full": that would delete every round it does not carry.
- Removes null values, then validates against the lenient schema. A document that fails gets 400
  or 422 with {"errors": [{"path": ..., "message": ...}]}.
- Keys a meet on omx.generator.id plus meet.id. Compares omx.updated_at, as an instant rather than
  as text, with the last document we applied for that meet, whatever its update. Newer: apply it.
  Equal: no-op, 200. Older: keep what we hold and answer 409.
- Applies a "full" document by REPLACING everything we hold for that meet: anything not in it is
  gone. Applies a "partial" document by replacing only the rounds it carries, each whole (its
  status, heats, and every entry in it; an entry it no longer lists is gone), replacing the team
  score blocks it carries by their id, adding or updating the meet, divisions, groups,
  waves, events, teams, and athletes it carries by id, and changing NOTHING else. A partial document
  never deletes anything. The same document twice is a no-op.
- Answers a "partial" with 412 and {"need": "full"} when we do not hold the meet, or cannot vouch
  that we hold everything we accepted (we restored a backup, or refused the first full with 413),
  until a "full" arrives.
- If we are also Registration and find the meet by our id in meet.external_ids, a document under a
  generator.id or meet.id we have not seen is a SECOND Meet Operations product: keep both and tell
  our user, never replace one with the other.
- Can create a meet we have never seen, without Registration existing anywhere.

And give our users a destination card: a screen that shows
{"omx_destination": {"id", "label", "url", "token", "deliver", "content"}} with a copy button and a
download as <label>.omx-destination.json, validating against
https://2timer.app/omx/1.0/schemas/destination-card-v1.schema.json. That is how a timer tells
their Meet Operations software to send results to us. Treat the card as a credential: show it only
to the person setting up the meet, and let them revoke the token.

Rules that are easy to get wrong:
- Read meet.origin: { id, name }, the product the meet started in. When origin.id is
  Registration's system id, records that came from Registration carry its id in external_ids under
  origin.id. When it equals omx.generator.id, the timer built this meet and no Registration ids are
  ever coming: do not wait for them or warn.
- id is always the writer's id. Every other product's id for the record is in external_ids, keyed
  by system id, on the meet, events, teams, and athletes only. Divisions, groups, waves,
  markers, and entries carry only their id; print their names.
- Join an entry through its athlete and event: the athlete's external_ids and the event's
  external_ids under a system we know (Registration's, or one such as anet). A relay entry joins
  through its team, relay letter, and event. A multi-event component entry joins through its
  athlete and the component event, whose multi_event_id leads to the multi-event. NEVER match by
  name.
- Key our storage on Meet Operations' ids, NOT on external ids. An athlete can acquire a Registration
  id later when a timer merges a walk-up against a roster. Keyed on Meet Operations' id that is a
  clean overwrite; keyed on the external id it is a duplicate.
- Expect one entry per round: an athlete who ran a prelim and a final is two entries with two ids.
  The round with "final": true is the event's result. On any other round, overall_place is the
  place across that round only. An event with no round marked final has no result yet. Never decide
  the final from a round's number or label.
- A round's status is upcoming, started, cancelled, unofficial, under_review, or official. A value
  we do not know means "not official". A cancelled round has no entries: show the event as
  cancelled, not "results to come".
- A round's scheduled_at is when it is planned to start, and it is the meet's only schedule (there
  are no sessions); group rounds by day from it. A heat's started_at is when that heat actually
  went. Neither changes a result.
- A results entry carries exactly one of status and overall_place. A place means the athlete
  placed; in a started round it is the place so far, which may still move. Otherwise the status
  says what happened: "e" is a heat-sheet entry (heat, lane, seed, no result), sent only if our
  destination asks for start lists; "s" is still competing with nothing to place yet (no result,
  maybe splits); dnf, dns, dq, fs, and nr are where an entry ended without a place.
- An event whose code we do not know is still shown, by its name, with places as given and marks by
  display; print its nr entries as NR.
- A multi-event comes back at two levels: the athlete's entry on the multi-event holds the points
  total as result (pt) and the standing, and each component event (with multi_event_id) holds an
  entry with that event's result and its multi_points. "points" is always team points, one value
  per entry, the same in every team score block that lists it. An incomplete team has
  complete: false and no place, points, or entries.
- result is always the gun time. A road race may add net_result (start line to finish), and the
  round's place_by says which one decided places.
- Keep entries whose athlete has no Registration id. Never drop one we cannot join: a dropped entry
  means incomplete results and a wrong team score, shown as if correct.
- Compare marks on result.value, never on result.display. Times store to thousandths even when the
  event shows hundredths, so "10.61" cannot separate 10.605 from 10.614. A mark in a unit we do not
  know is shown by its display and never compared or sorted.
- A place, not a status, means the entry placed. Only a placed entry and a dq carry a result: a dq
  KEEPS its recorded result and holds no place; dnf, dns, fs, and nr carry none. "nr" (no result)
  prints as NT for a race, NM for a throw or horizontal jump, NH for a vertical jump, never "NR";
  NT/NM/NH are never written as a status.
- Never recompute a place, a tie, or a team score. They arrive computed. Two entries in one round
  with the same place are tied; there is no tied flag.
- Ignore fields we do not recognise. Never fail over one.
- A value we do not know in an open list never fails an import. Follow the table in the spec's
  "Open and closed lists" section.
- Publish names as they arrive. A results document carries no birth dates, contact details, or
  notes; if one ever does, never publish it.
- Splits name where they were taken: a marker from the event's markers, or a relay leg. Each split
  is one number, time_ms, elapsed from the gun; subtract two to show one stretch.
- Team standings may list the entries that scored or displaced, each with its role (scorer or
  displacer); each entry's own points is its value. Print a cross country team line from that,
  never by recomputing. A scope.gender separates boys' and girls' scores.

Two things I need to decide, so ask me rather than assuming, and explain each in terms of what our
users would see on our pages:
- "deliver": "final" (the default) means full documents once every round is official, plus any
  correction; "progressive" means a first full document, then partial ones as each round is seeded,
  goes official, or changes; "live" adds a partial document every few seconds while a round runs.
- "content": "results" (the default), or also "start_lists" for heat sheets before a round runs.

Write tests that POST each fixture in https://2timer.app/omx/1.0/fixtures/index.json whose kind is
"results" and check we answer as its "expect" says, sending c30-first before c30-older,
c30-same-instant, and c111-partial. Then deploy to a test URL and run the Publisher checks on
https://2timer.app/omx/test against it, which also proves browsers can reach us.

Finish by running conformance group C (OMX-C22 to OMX-C46, OMX-C82 to OMX-C84, OMX-C90 to OMX-C92,
OMX-C99, OMX-C101, OMX-C109 to OMX-C114, OMX-C120, and OMX-C121; OMX-C125 only if we answer the
optional probe) and OMX-C107, and show me the results.

Prompt 3 — Add OMX to timing software (Meet Operations)

Use this if you already have timing software and want it to read meets and hand the results on. It covers the exchange only: your product already decides rounds, seeding, ties, statuses, and scores, and this prompt maps them into and out of OMX without computing any of them.

Our product is timing software. I want to add Open Meet Exchange (OMX) as "Meet Operations": read a
meet from a pasted registration URL or a file, and hand the results on to the destinations
(Publishers) the document names or the timer adds.

Read these first, and treat them as the authority over anything you remember about meet formats:
- Spec:     https://2timer.app/omx/1.0/spec.md
- Reader schema:   https://2timer.app/omx/1.0/schemas/meet-document-v1.schema.json (validate what we read)
- Results schema: https://2timer.app/omx/1.0/schemas/results-document-v1.schema.json (validate what we send)
- Examples: https://2timer.app/omx/1.0/examples.md, each registration document with its results twin
- Tests:    https://2timer.app/omx/1.0/conformance.md, group D
- Fixtures: https://2timer.app/omx/1.0/fixtures/index.json
- Checker:  https://2timer.app/omx/1.0/tools/omx-check.mjs
- Mapping:  https://2timer.app/omx/1.0/mapping.md, if we already import Hy-Tek or other formats

Our implementation is against OMX 1.0, and reads any 1.x version.

THE MOST IMPORTANT RULE: you do not compute anything. Places, ties, team scores, qualifiers, and
points come from our existing code, exactly as our product already computes them for its own
results. You map our data into the document and out of it. If our model is missing something the
document asks for, say so and omit the field; never write a guess.

Before writing any code, inspect our codebase and tell me:
1. Where meets, events, rounds, heats, teams, athletes, entries, and results live.
2. Whether every record has an id that never changes for the life of a meet. A results document
   carries OUR ids in "id" and Registration's ids in "external_ids" under Registration's system id
   (its omx.generator.id) on the meet, events, teams, and athletes, so we must store those ids on
   each record we create from a document, with every other system's id it carries, including
   systems we do not know. Entries carry no external_ids, but keep Registration's entry id
   ourselves, so a later fetch of the same link can be merged.
3. How we record statuses (DNF, DNS, DQ, scratches) and how we store times and marks: units and
   precision. OMX stores times to the millisecond and imperial marks in inches, never converted.
4. How rounds work in our model, how we know which round is the final, and whether a round can be
   cancelled.
5. What our results and live-results publishing already sends, and how often.

Then propose the mapping in both directions and stop for my review before implementing.

Then build, in this order:
1. Read a document from a pasted https:// URL or an opened file (.omx.json or .json, recognised by
   its top-level "omx" object). Read omx.version first, and refuse any version but 1.x (a draft
   such as "0.9" included), naming the version we read. Read omx.kind next: refuse "results"
   before creating anything, and tell the timer that a meet is moved between computers with our
   own backup; refuse a missing or unknown kind, naming what it found. Never work out the kind from
   the document's fields. Remove nulls, validate against the reader schema, and build the meet with
   no wizard: venue, schedule, divisions, groups, waves, events, teams, athletes, entries. Refuse
   http://.
   A field Registration does not write (a result, a place, a bib on an entry, rounds, markers) is
   ignored and reported to the timer, never a reason to refuse the meet. A badly formatted value
   (a three-letter country, a 20-character team code, a short token) is a warning to the timer,
   never a failed meet; when we write such a value back, we fit it, such as a shortened team code
   unique in the meet.
   An event whose code we do not know is left out with its entries, and the timer is told; the
   rest of the meet is read. A sport we do not run is refused, naming it. An adaptive event is never
   merged into the standard event of the same code. A multi-event (DEC, HEP, PENT, any gender) gets
   its component events from our own rules, with the spec's table as the default; one we cannot
   build is reported, never guessed from its name.
   Fetching the same link again later returns a complete registration document of the same shape.
   How we merge it into the meet is our own decision; never let it undo the timer's own work.
2. Destinations: from the document's publish array, and added by the timer, including by pasting a
   destination card ({"omx_destination": {...}}), which updates a destination with the same url
   rather than adding a second. Each has a "deliver" (final, progressive, live; an unknown value
   reads as final) and a "content" (results, start_lists; an unknown value is ignored).
3. Hand off: POST to each destination's url with Authorization: Bearer and Content-Type:
   application/json, the body sent as is or with Content-Encoding: gzip. Every document is
   "kind": "results" with "update" "full" or "partial":
   - "final" destinations get full documents only, once every round is official or cancelled, and
     again for each correction. "progressive" get a first full document, then partial ones as each
     round is seeded, goes official, or changes. "live" also get a partial document every few
     seconds while a round runs. We may always send full instead of partial.
   - A partial document carries whole rounds: each round's status, timing, heats, and every entry in
     it; the events of those rounds with only those rounds (a multi-event component brings its
     multi-event and the multi-event's round); every athlete, team, division, group, and wave those
     entries name; the event-scope team scores of its events; and every division- and meet-scope
     team score, whole. Every team score block has an id that stays the same between documents.
   - At most one document in flight per destination. Keep, per destination, the rounds changed since
     it last accepted a document; build every send, a retry included, at the moment it goes out,
     carrying all of them. The first send from any copy of the meet is full, and so is every send
     after anything is removed (an event, round, team, athlete, division, group, wave, or team score
     block), and the next send after a destination answers 412.
   - Write our system id as omx.generator.id, and meet.origin as { id, name }: Registration's for
     a meet built from its document, our own for one the timer built. Write our own id in "id" on
     every record, and every external id we received, unchanged, in external_ids on the meet,
     events, teams, and athletes; none on entries. A meet made from another meet (next year's from
     this year's) is a new meet with new ids and none of the old meet's Registration ids.
   - Write omx.updated_at to the millisecond (stamp max(now, previous + 1 ms), stored with the meet),
     one entry per athlete or relay per round with "round", "final": true on the round whose places
     are the event's result, and each round's status: upcoming, started, cancelled, unofficial,
     under_review, or official.
   - An entry's status fits its round: an upcoming round holds e entries only, and only for a
     destination whose content includes start_lists. Every other entry carries exactly one of
     overall_place (it placed; in a started round, its place so far) and a status: s while still
     competing with nothing to place, or dnf, dns, dq, fs, nr. A cancelled round holds none. A
     scratched or wait-listed athlete has no results entry.
   - List only the athletes the entries name (as the athlete or in a relay's legs) and their teams.
     An athlete's age is the age we placed them by; never write a birth date, email, phone, chip, or
     notes. The bib is the athlete's, the one they raced under. No publish array, no tokens, and no
     other product's extra fields.
   - Write a multi-event at both levels: every component event with multi_event_id in a full
     document, the multi-event entry's total as result in pt, and each component entry's
     multi_points. Splits name a marker the event lists or a relay leg, with time_ms elapsed from
     the gun. A race's wind is on its heat; a field entry's wind_mps is the wind of the mark that
     counted. Gender is written as stated. Never write null.
4. Responses: done on 200/204; retry 429 after Retry-After and 5xx or no answer with backoff,
   rebuilding the document at send time; on 412 send a full document next, at once; never retry
   400, 401, 404, 409, 410, 413, 422, or any other 4xx, and show the timer which destination failed
   and why, with any "errors" in the body.
5. Offer the timer a cadence for each destination, with its "deliver" and "content" preselected.

Add tests: every handoff we produce for each example meet, full and partial, validates against the
results schema and passes omx-check.mjs with zero errors; reading each "registration" fixture in the
fixture index behaves as its "expect" says.

Finish by running conformance group D (OMX-C50 to OMX-C53, OMX-C55, OMX-C61, OMX-C63 to OMX-C77,
OMX-C79, OMX-C85 to OMX-C87, OMX-C94, OMX-C96, OMX-C97, OMX-C102, OMX-C104, OMX-C105,
OMX-C115 to OMX-C119, and OMX-C122 to OMX-C124) and OMX-C107, and show me the results.

These prompts are dedicated to the public domain under CC0 1.0.