Work Protocol

The methodology says what counts as evidence and what the rebuild may change. The documentation standard says how each finding is written down. Neither says what to do first, where open questions are tracked, how big a piece of work is, or what the next person or agent needs to pick it up. The rules here apply to people and coding agents alike, and to every game repository whose spec follows the documentation standard, whether it was started from the project template or not. A repository that runs an original on a machine it shares with one of them takes the run lock as well. The Dark Sun: Wake of the Ravager restoration is planned and tracked this way, with its open questions in queue/ and its live session requests in docs/live-sessions/, and the template ships the working files and the agent skills described below.

Working files

The files that track the work sit in the game repository next to the spec. They describe the work and hold no findings, so the spec’s rule against naming the rebuild does not apply to them. Anything an attempt learned about the original is recorded in the spec first, and a working file points at the entry. Each one says what is true now. What was true before is in git, and none of them repeats it.

FileHoldsNever holds
docs/IMPLEMENTATION-PLAN.mdThe game profile, scope and non-goals, the stage the project is in, the slices with their exit criteria, the risks, and the questions only the owner can answer.Dated checkpoints, status narration, research questions, lists of what was done.
queue/AREA.mdOne file per spec area: the open research questions of that area, grouped by the kind of evidence each one needs.Anything already answered. An item is deleted when it is settled.
docs/RUNTIME.mdWhat can be done with the original running, and who can do it (see Runtime access).Findings. What a run showed goes in the spec.
docs/live-sessions/NAME.mdOne file per live session an agent has asked for: its status, the items it would settle, how long it takes, and its script (see Live sessions). Deleted when the session’s recordings are in the spec. The files in the directory are the list of open requests.Results. What the session showed goes in the spec.
docs/HANDOVER.mdWhere the work outside any goal stands: the last gate result, work left unfinished, blockers, and what to pick up next, naming queue items and entries by ID. At most 200 lines, rewritten at the end of every session that works under no goal, added to by the commit that deletes a goal file, and otherwise changed only where a research batch moves a citation of an entry it supersedes.History. The previous session’s notes are replaced, not kept below the new ones. The branch, its commits and whether it is pushed, which Git always has more current. What research found or tried, which belongs in the spec and the queue. The goals running and the live session requests, which the files in docs/goals/ and docs/live-sessions/ list (and, for a goal that runs on a branch, the goal/ branches).
docs/reports/R-NNN.mdOne file per report from testing that has not been triaged yet, or that is waiting for what it is missing (see Reports from testing).Screenshots, which stay in the local store. What triage decided, which goes in the spec, the queue and the parity rows.
docs/goals/NAME.mdOne file per long-running goal while it runs: its condition, its side (research or implementation) where it names one, the areas it claims, what it must not touch, the dead ends its tools and approaches have hit, and its own handover with the same content as docs/HANDOVER.md, rewritten at the end of every session under the goal. Deleted when the goal is met or dropped, and whatever in its handover is still worth handing on, such as a blocker, moves to docs/HANDOVER.md. A copy that reaches the main branch when the owner merges a goal/ branch claims nothing (see Coding agents and long-running goals).Progress reports. The queue and the commits show progress.

Decisions the owner makes go in docs/DECISIONS.md, dated and newest first. A decision that departs from the original is a deviation as well, in deviations/. When the file would pass the size limit below, its oldest entries move to a numbered file in docs/decisions/, starting at 001.md, filled up to the limit and never changed after. docs/DECISIONS.md keeps the newest entries and opens with links to the numbered files, and a decision is never renumbered or reworded when it moves.

Every file here is at most 1,000 lines, the same limit the standard sets for its own files. A queue file that would pass it becomes a directory of the same name, with a README.md holding the area’s heading and its Next ID: line (see The queue) and one file per kind of evidence that has items, named after the section in lower case with a hyphen for the space: queue/COMBAT/static.md, queue/COMBAT/agent-run.md. Each file opens with the area and the section as a # heading, # COMBAT: Static. A file that would still pass is split by the kind of the first entry each item names, queue/COMBAT/static/RULE.md, the same way the standard splits its own files.

Tool paths, installed versions and the pitfalls of the local setup go in docs/GHIDRA.md and docs/DEVELOPMENT.md, so that nobody searches for them twice.

Where the template’s files and this page disagree, this page applies and the template is corrected.

Stages

The plan records the stage the project is in. The stages come in the order below, and a stage ends when its exit criteria hold, which a script or a reviewer can check without asking anyone. Work from a later stage may start early, but it does not count towards that stage’s exit until the earlier stages have ended.

Intake

Establish which game, which editions the owner has, and whether the game qualifies. Find the latest official version and patch the analysis copy to it. The template’s AGENTS.md and bootstrap checklist give the steps.

Exit: eligibility and the latest version are recorded, the analysis build has a build entry and a manifest, and each manual, FAQ or earlier tool the work will rely on has a source entry.

Runtime access

Find out what can be done with the original running, before any plan depends on it. Evidence from the original comes from four places. Reading the executable and the data files is the main one for every game. Calling single functions of the executable in a CPU emulator checks those readings for everything the code decides, with no window and no timing involved (see Emulated calls). People who play, in live sessions and in the testing that reports come from, see what the original and the rebuild do on screen. Runs an agent drives on its own come last, since many of these games cannot be controlled by an agent at all, and where one can, the run is the most fragile evidence the project has (see Running the original).

docs/RUNTIME.md records, for each build that will be run, how it runs (natively, under Wine, in DOSBox-X or another emulator, in a virtual machine), and whether an agent can do each of the following alone, only while a person runs the game, or not at all:

  • start the original and bring it to a given state without a person;
  • send it input;
  • read its memory, set breakpoints and dump structures while it runs;
  • load a patched save;
  • capture frames and sound;
  • play back a recording the original made;
  • call a single function of the executable in the emulator harness (see Emulated calls).

Several of these name more than one thing, and the parts can get different answers. A game may take keyboard input from an agent and mouse input only while a person plays, or let a capture tool record its frames but not its sound. Where the parts differ, the record gives each part its own answer under the capability, such as keyboard: agent, mouse: person, frames: agent and sound: none. For input, person means a person has to give that input: the agent sends none to a game a person runs (see Live sessions), so the run becomes a live session in which the maintainer plays. The parts of a capability are the ones the game has: for input, each device the game reads, and for capture, sound only if the game makes any. A single answer for the capability stands for every one of those parts, so it is written only when every part was tried and got that answer. A run uses the answer of each part it needs: it cannot be made if any of those parts is none, it is a live session if any is person, and it is an agent run only if every one is agent. An item that needs only the keyboard can be an agent run in a game whose mouse needs a person, and its experiment’s Setup says it used only the keyboard.

Finding the answers is the one time the original is run before the static work is done, and it takes the run lock like any other run. The emulator harness starts no process of the game, so checking it takes no lock. Its answer is agent wherever the harness loads the build, whoever may run the game, because emulated calls are always allowed. Until the harness exists the answer is none, and what would change it is the tooling batch that builds it. Each answer names the tool and version that was tried and what happened, and, where the answer is no, what would change it. The record says what is true now, and it is updated whenever a tool, a machine or an emulator changes the answer.

Exit: docs/RUNTIME.md answers every capability for the analysis build, or every part of it where the parts differ, and each answer names the attempt it comes from.

The answers decide where a run goes in the queue: an agent run where an agent can make it, a live session where it needs a person. They also set what the project can reach. An entry reaches established through a complete reading of the code alone, unless what it says depends on something the code does not decide, such as timing, an interrupt or the operating system, and only then does it need a run that agrees with the reading. A reading whose procedure an emulated call agrees with, on every branch, establishes it as well, once the reading of its callers and inputs is complete. A rule or screen row reaches validated only through tests that compare the rebuild with evidence recorded from the original running. An emulated call is such evidence, so a rule the code decides can reach validated even where nobody can run the game. A format row whose entry lists files can reach validated without any runs, because its evidence is the original files. A format with no files, a memory structure or a message, is compared with what was recorded from the original running, so it needs a run the same way a rule does. A project whose record says no agent can run the game plans its slices around that, and keeps the rest for live sessions and for what testers’ captures of the original turn up.

Survey

Map the whole game shallowly before studying any part of it deeply. List the installation and the media the game reads in full, and record how the listing was made, in a listing record where the build keeps one. List in the manifest every file the game uses, and the rest of the listing under the build entry’s Other files, as the standard’s Builds section describes. Give the format of every file the manifest lists as data an entry, unknown where nothing is known yet. A CD audio track has one fixed layout and needs no format entry. Take an inventory of the functions of each file the analysis reads, as Measuring progress describes. Write down the screens the manual or a playthrough shows, and the rules the manual states, as sourced entries. Fix the area list, since areas can never be renamed. Seed the queue with the questions this turns up.

Exit: every path in the listing of the installation and the media the game reads, other than the members of archives the listing went inside, is in the manifest or in the build’s Other files, every file in the manifest with format data is listed by a format entry, every screen the manual mentions has a screen entry, every file the analysis reads has a function inventory committed in coverage/ (see Measuring progress), and every area has a queue file.

Checking the format definitions against some of the files, such as the few whose hashes identify the release, says nothing about the rest and does not pass this gate. Until every path in the listing is accounted for, the plan records what is still missing and Survey stays open.

A survey keeps later work from being planned around the one subsystem somebody happened to open first, and it makes the size of the game visible before anyone estimates it.

Slices

Build the game in vertical slices, each of which leaves it playable. The plan lists the slices in order, and each slice names the spec areas or entries it needs and the parity rows it has to bring to implemented, deviated or validated. Each target is one the runtime record makes reachable, and where a row needs a live session nobody has agreed to yet, the plan asks for implemented and names the gap in its risks. The first slice gives the rebuild a headless runner that a test drives from a fixture, since every later test that compares the rebuild with the original needs one. Within a slice, research and implementation alternate in small batches, described below.

Exit, per slice: every parity row the slice names has reached the status the plan asked for, and every queue item that blocked the slice, named by its ID, is closed or accepted as a known gap in the plan’s risks.

Audit

Close the gaps the slices left. Every function in the inventories is cited by an entry or recorded as out of scope with its reason, every parity row is validated, or deviated where a mandatory deviation names it in its Replaces item, or has a note saying why it can be neither, the queue holds only items the owner has accepted as out of reach, and every open report is waiting for what its Missing: line names.

The queue

The queue holds the research questions still open. Every item names the spec entries it concerns, so behaviour with no entry gets an unknown entry before it gets a queue item, the same way it gets one before any code. The entry’s own Open questions section describes what is unknown about it. The queue item says what to do next about it, and an item that duplicates the whole Open questions section is not needed.

A queue file opens with the area as a # heading and a line giving the ID the next new item takes, Next ID: Q-COMBAT-013, and has one ## section for each kind of evidence, in this order:

  1. Static: a reading of the executable or data files settles it.
  2. Emulated call: a call of one function of the original in the emulator harness settles it (see Emulated calls).
  3. Agent run: a run of the original that the runtime record says an agent can make alone.
  4. Live session: a run that needs a person to run the original while an agent measures it (see Live sessions).
  5. Source: a document that has to be found, bought or read.
  6. Blocked: work stopped until something else changes.

An item is a single list entry:

- Q-COMBAT-012. RULE-COMBAT-012, FMT-STATE-001: Which of two gangs attacking
  each other rolls first? Settles it: the order of the two calls at the
  resolver's entry, and one experiment from a save where both attack.
  Blocks: slice 4.

It gives its ID, the entries, the question as a question, what would settle it, and the slice it blocks or none. An item asks one question that one piece of evidence can settle. A question whose parts can be answered separately, such as a formula and the caller that decides when it runs, is split into items before anyone works on it. It goes in the queue file of the area of the first entry it names. The ID is Q-, the area of the file it was created in and a number, taken from the file’s Next ID: line, which then goes up by one. It is never reused, even after the item is deleted, and it stays the same when the item moves to another section or another area. When an item is split, one part keeps the ID and the others take new ones. Everything outside the queue refers to an item by its ID: the handovers, live session requests, slice exits, parity row notes such as Spec gap:, deviations and status blocks. Two branches that create the same item ID are handled the way the standard handles two that create the same spec ID.

When the runtime record changes, the items it affects move between Agent run and Live session in the same commit. Where the record answers the parts of a capability separately, an item under Agent run or Live session says in what settles it which of those parts its run needs, such as only the keyboard, so that the items a changed part affects can be found. An item that has already been worked on adds Tried: saying what was examined and why that did not settle the question, so that nobody repeats it. What the attempt did learn about the original goes in the spec as a finding first, and Tried: names the finding. An item under Blocked adds Waiting on: with what has to change first.

An item is closed by recording its answer in the spec (a finding or an experiment, and the status of the entries it concerns) and deleting the item in the same commit. An item that turns out to be two questions becomes two items. A new question found along the way becomes a new item at once, even if nobody will look at it for months.

A static reading that settles an item raises its entries as far as the reading goes. A complete reading, as the standard defines it, makes an entry established with no run, and that is how most entries are meant to get there. A reading that settles the question without being complete leaves the entry supported, and the same commit adds a Static item for what the reading still has to cover, such as a caller nobody has found or an indirect call nobody has resolved, and an Emulated call item where the harness can reach the functions the reading covers. Only an entry that depends on something the code does not decide needs a run. For such an entry, where the runtime record says an agent or a person can make the run, the same commit adds an item under Agent run or Live session for the experiment that would confirm the reading. Where nobody can, it adds none, and the entry’s Open questions section says which observation of the original would confirm it, so that a capture that arrives later with a report can.

An attempt that does not settle an item records what it tried under Tried:, one note per attempt, and the item is taken up again only with something the earlier attempts did not have: new evidence, a new tool, or a reading of the code nobody has tried. An attempt ends in a new place only if it recorded a finding that answered part of what the item’s Settles it names and took that part out, or a finding that showed part of the question can be settled on its own and moved that part into an item of its own. Otherwise it ends in the same place, however much code it read that nobody had read. That includes an attempt that rewrote Settles it to name a caller or callee one step further on, where the same obligation now sits.

Once two attempts in a row have ended in the same place, the batch that made the second one splits the item or moves it. An item that already has two such attempts is split or moved by the next batch that takes it up, before it tries anything else. The item is split when the attempts have shown parts of Settles it that can be answered separately, such as callers in a list that need evidence the others do not, several obligations a reading could each close, or a value the code reads at one point together with what follows from each value it can hold. Each part gets its own Settles it. The part that still asks the item’s question about what is left keeps the ID, so that a Spec gap: note or a handover naming the item still points at that question, and the other parts take new IDs. Each part takes one Tried: note naming the earlier findings that concern it, and its own attempts are counted after that note. An item that is one question moves to the section of the evidence that would change the outcome, with what was tried: Emulated call, Agent run or Live session for a run, Source for a document. It goes to Blocked only when that evidence is out of reach for now, such as a tool nobody has, a second edition the owner does not own, or a run the runtime record says nobody can make, and its Waiting on: names what is missing.

Here two attempts each read code nobody had read, and neither changed what would settle the item:

- Q-COMBAT-020. RULE-COMBAT-018: Does any of the round handler's eleven
  callees change a gang's morale? Settles it: every store to the morale
  byte made by the callees or by the code they hand off to. Blocks: slice 5.
  Tried: FND-COMBAT-040, a search from the handler for stores to the byte,
  found none but stopped at two indirect calls.
  Tried: FND-COMBAT-044 reads the two callees with indirect calls. Both pick
  their target by whether the display mode word is 0, and the word's value
  at the call is unknown.

The batch that recorded FND-COMBAT-044 splits the item by what each part needs. Q-COMBAT-020 keeps the morale question for the nine callees without indirect calls, and Next ID: goes from Q-COMBAT-031 to Q-COMBAT-034:

- Q-COMBAT-020. RULE-COMBAT-018: Do the round handler's other nine callees
  store to the morale byte? Settles it: a complete reading of each of them.
  Blocks: slice 5.
  Tried: FND-COMBAT-040 found no store to the byte in the code it reached.
- Q-COMBAT-031. RULE-COMBAT-018: Which values can the display mode word
  hold when the round handler calls its callees? Settles it: every store to
  the word that can run before that call. Blocks: slice 5.
  Tried: FND-COMBAT-044 found that two callees pick their target by the word.
- Q-COMBAT-032. RULE-COMBAT-018: Does the code the two callees reach while
  the display mode word is 0 store to the morale byte? Settles it: a
  reading of the targets for that value. Blocks: slice 5.
  Tried: FND-COMBAT-040 stopped at the two callees' indirect calls, and
  FND-COMBAT-044 found that they pick their target by the word.
- Q-COMBAT-033. RULE-COMBAT-018: Does the code the two callees reach while
  the display mode word is not 0 store to the morale byte? Settles it: a
  reading of the targets for that value. Blocks: slice 5.
  Tried: FND-COMBAT-040 stopped at the two callees' indirect calls, and
  FND-COMBAT-044 found that they pick their target by the word.

If Q-COMBAT-031 shows the word can hold only 0, Q-COMBAT-033 is closed by the same finding.

Order of work

Items are picked in this order, from every section alike. Within each step, an item under Static comes first, then one under Emulated call, then one that needs a run of the game, and Running the original says when a run item can be taken up at all.

  1. Items that block the current slice.
  2. Items that others depend on: the random number generator, the main loop and the order of its phases, the save format and the structures the game keeps. Most other experiments need these.
  3. Items where one piece of evidence raises the most entries. A complete reading has to meet every condition the standard sets for one, and an experiment that raises an entry to established has to cover everything the entry says, every branch of a procedure and, for a random outcome, enough repetitions for its comparison, and a rule stays below established while any glossary claim it relies on is (unknown). Where the experiment covers only part of the entry, the entry stays supported and its Open questions say which part is left.
  4. Items with the cheapest evidence: a data file before a static reading.

The life of a claim

A claim is what a rule, format, screen or bug entry says the original does. It changes only in a research batch, when the evidence changes, and the entry’s status says how far that evidence goes.

A claim starts as a listing. The Survey, an implementation batch that meets a gap, a report or a function no entry cites gives the behaviour an unknown entry, which claims nothing yet, and a queue item. A manual or another outside source can give the entry a description at once, at sourced, and only evidence from the original takes it further.

Research then forms the competing readings. A reading is plausible while the evidence for it is only circumstantial: sizes that divide exactly, a pattern in a file’s values, a name, the manual, what similar games do. Such evidence is recorded as findings but never raises a status, as the standard’s direct evidence rule says. While more than one reading is still standing, or the one left rests only on circumstantial evidence, they sit in the entry’s Open questions section, each with what it claims, the findings and experiments for and against it by ID, and the evidence that would rule it out, and each has a queue item whose Settles it names that evidence. Outside the spec a reading is cited by its queue item’s ID, and the commit that closes the item names it in a Queue: trailer, so the reading can be found in the history after the item is gone. Readings get no IDs of their own. An entry whose readings are all still open stays unknown, or sourced if a source describes it, whatever evidence the readings cite.

When direct evidence, the code that produces the behaviour, rules out every reading but one, that reading becomes the entry’s description at supported, and the readings ruled out move to the Alternatives section of the finding that ruled them out. A description says no more than its evidence shows. What the evidence does not reach stays in Open questions, as readings or as questions. A source that disagrees with the findings stays cited, with the disagreement written in the entry’s body and in the source’s Known errors section, as the standard says.

The entry is corroborated, and reaches established, when the reading of the code is complete: every branch, every caller and every write to what it reads, every indirect call resolved, and nothing left to timing, interrupts or the operating system. Most entries get there by reading alone, and the entry lists the findings of that reading in complete_reading. A reading that is not complete gets there when an emulated call agrees with it on every branch the entry describes, provided the reading of everything outside the function is complete: every caller, every write to the state it reads, and so everything the entry’s When it runs and Inputs sections say. An entry that does depend on something the code does not decide gets there only when a run of the original agrees with the reading, and the run can be an agent run, a live session, or a capture a tester sent with a report. A parity row still reaches validated only through tests against evidence recorded from the original running, as the standard requires, so a rule or screen row established by reading alone stops at implemented until such evidence exists. For a rule the code decides, that evidence is usually an emulated call’s fixture.

Evidence that contradicts the description makes the entry disputed at any point, even after its row was validated, and the row shows disputed until the dispute is settled. If the contradicting finding or experiment turns out to be wrong, it is superseded, and the entry goes back to the status its remaining evidence supports. If the description turns out to be wrong, the entry is superseded by a corrected entry, by the parts it splits into, or, when the mechanic does not exist, by the findings that show it, and citations and code follow superseded_by as Research batches describes. A superseded entry is never deleted, and it keeps the links it had.

Implementation meets a claim only as the entry’s description, or as a question. An open reading never reaches code: a row whose entry is unknown cannot be complete, and code that needs an open reading stops at a Spec gap: note or a PLACEHOLDER: comment.

Batches

A batch is one unit of work and ends in one commit, or one pull request where the repository uses them. It is small enough to review in one sitting, and it leaves the documentation check and the fast validation gate passing. A batch is research, implementation or tooling, and never more than one of them.

The handover commit at the end of a session (see Sessions) is not a batch, and neither is a commit that creates a goal file, changes the areas it claims, or deletes it when the goal is met or dropped and moves what is still worth handing on to docs/HANDOVER.md. These commits change nothing outside docs/HANDOVER.md and docs/goals/, leave the documentation check and the fast validation gate passing, and carry no trailers, since they change no entries, parity rows, queue items or reports. The batch whose work meets a goal’s condition leaves the goal file in place, and a later commit deletes it. A session’s own goal, met or dropped during the session, loses its file in that session’s handover commit. Any other goal, such as one dropped between sessions or found to have been met by an earlier batch, loses its file in a commit of its own.

Research batches

A research batch settles one queue item, a few items about the same entry, or the items of one area that one live session’s recordings settle, or it triages one report from testing. It states the question, forms the competing readings, looks for the evidence that could rule each one out, and records the result as findings or experiments under the standard. Readings that are still open when the batch ends are written down, never left in the session’s memory. They go in the Open questions section of the entry they concern, one per reading, each with what it claims, the findings and experiments for and against it by ID, and the evidence that would rule it out, and each has a queue item whose Settles it names that evidence (see The life of a claim). A reading that the evidence rules out moves to the Alternatives section of the finding that ruled it out, and one that survives becomes what the entry says, at the status its evidence supports. A reading never reaches code on its own: an implementation batch builds only what an entry says, and meets an open reading as a question. The entries the evidence concerns take the status it supports, the item is deleted or updated with what was tried, and new questions become new items. The batch leaves the documentation check passing. Outside the spec and the queue it makes these changes and no others:

  • where an entry’s status changes, the Spec status and Status columns of its parity row, and where it becomes disputed, a note in the row’s Notes naming the evidence in the entry’s conflicting, which goes again when the dispute is settled;
  • for a new rule, format or screen entry, a parity row with Code missing, and for one that is retitled, the Title of its row;
  • for an entry it supersedes, its row is removed, and every citation of it in src/, tests/, tools/, parity/, the deviations that have not been dropped, docs/HANDOVER.md and the goal files in docs/goals/ moves to the entries its superseded_by names, all of them where it names several. A dropped deviation’s Departs from and Replaces items stay as they were when it was dropped, as the standard says;
  • where code was written for a superseded entry, each rule, format or screen that replaced it takes Code partial in its row and a note naming the old entry, so that an implementation batch checks the code against the new one. Where only findings or experiments replaced it, because the mechanic does not exist, the code cites them from then on, and the next implementation session removes it (see Implementation batches);
  • where the batch changes in place what a rule, format or screen entry says the original does, such as a step or branch of its procedure, a value in one of its tables, a field of a layout or an edge case, or what a bug entry says about how it occurs in one, and that rule’s, format’s or screen’s row has Code partial or complete, Code partial and a note at the start of the row’s Notes: Defect (FND-COMBAT-031): with the findings or experiments that changed the entry, followed by what the entry says now in place of what it said before, in the spec’s terms only. An implementation session fixes the row as Reports from testing describes. There is no note, and Code stays as it is, when the batch changes only an entry’s status, evidence or wording, only a part of the entry that a deviation listed in the row replaces, or only answers the question of the row’s Spec gap note, which it removes as below. An entry whose description turns out to be wrong is superseded instead, and its rows change as the two items above say;
  • where a deviation’s Departs from moves to a new entry, the Deviations column of that entry’s row;
  • for a Spec gap: note, the queue item’s ID when the note becomes an item, and the note’s removal when the item is closed;
  • for a queue item it closes, every citation of the item’s ID in parity/ and in the deviations that have not been dropped, other than a Spec gap: note. The citation moves to the findings or experiments that answered the item and, where they left part of the question open, to the new items that hold that part. A live session request that lists the item drops it, with the steps of its script that only that item needed, and a request left with no items is deleted. Handovers and findings that cite the item as a record of earlier work stay as they were;
  • for a report that shows the rebuild departing from the spec, Code partial in place of complete and a Defect (R-NNN): note, as Reports from testing describes;
  • PARITY.md and spec/index/, which the documentation check writes again, except in a repository that updates them on the main branch only, where the batch leaves them as they are.

Moving a citation changes an ID and nothing else, except for the words around a closed queue item’s ID that said its question was still open, and a citation in tools/ whose value rested on the wrong observation, which IDENTIFIERS-8 says how to correct. Apart from that, code is not changed, beyond tools in tools/ the research needed.

For an item that asks whether the original ever does something, such as start another program or write to a file, the batch first names the mechanisms every use of that capability has to pass through and takes a census of their sites across all the code the item covers, as the documentation standard describes under Findings. Tracing the code that prepares the action, such as the routine that builds a path name and every routine it passes the name to, cannot show that the capability is never used until every callee, every writer of the values they depend on and every alias has been read, which can take more batches than the item is worth. Only what the census leaves undecided, such as a site whose selecting value is loaded from memory or a transfer to code outside the census, is traced further, and each of those becomes a queue item of its own.

Implementation batches

An implementation batch brings parity rows of the current slice up to implemented, deviated or validated. It works from the spec only. It does not open Ghidra, a decompiler listing, a debugger log or research notes, and it learns what the original does by reading spec entries. Where the spec does not say enough to write the code, the batch stops at that point and writes down what the code needs to know as a question in the entry’s Open questions section. Where no entry describes the behaviour at all, it creates an unknown entry that holds only that question, with its parity row. It then either leaves the row partial or writes the code with a PLACEHOLDER: comment citing the entry, and it starts the row’s Notes with Spec gap: and the question. The next research session turns each such note into a queue item and adds the item’s ID to the note, Spec gap (Q-COMBAT-014):, so the note shows that research has it. The note stays until the research batch that closes the item removes it, and from then on the row is partial with no note, which tells an implementation batch that the spec now answers the question. An implementation batch never reads the queue, since its items hold what research tried.

Every decision the original makes, whether a gate, a chain of flags, a branch table, the choice of an outcome or a change of state, lives in the rebuild’s rules layer as a function of the serializable game state and its inputs, even where only the presentation calls it. The presentation turns those decisions into screens, art, input and timing, and is never the only home of a rule. A rule already written inline in a screen handler moves to the rules layer the first time a batch touches it, and the move is tested as Rules behind an adapter says. If showing a behaviour needs a window, a graphics device or the original’s assets, its rule is not in the rules layer yet. Each entry’s branches are implemented whole: every branch the entry describes has its own test over synthetic state, including the branches the presentation cannot reach yet and the arms of a guard that look impossible. These tests compare the rebuild with the entry, so the row’s Tests column does not list them and they never make the row validated (see the standard’s Parity matrix). A branch that lives only in a comment is the one that gets implemented inverted, and a play session reaches only the branches it happens to take. A bug traced to branch logic in the presentation is fixed by moving the rule into the rules layer, testing every branch, and fixing it there.

Where the rebuild computes a rule with a different algorithm from the procedure in its entry, the claim that the two agree names the state and outputs it compares, including whatever a later call reads. For a search that keeps a queue of work, the tests cover a node queued twice, a node whose stored score improves while an older queue entry for it is still waiting, and a search that stops at its step limit straight after adding a node’s successors and is then resumed. For other algorithms they cover the limits and the failure paths the entry describes. Two algorithms that reach the same result can still leave different state behind, raise events in a different order or carry on differently when called again, so a matching result shows only that the results match. These tests compare the rebuild with the spec, and like any other test over synthetic state they never raise an entry’s status or validate a parity row.

When a batch connects the parts of the rebuild that use a rule, it tests the intermediate states the spec describes. It follows one position or other shared value from where it is set up, through input, the rules layer’s updates and the presentation, and through a save and a restore, with a different value on each axis so that swapped coordinates or fields show up. Each change the rules layer makes visible is compared with the presentation that shows it: which object it belongs to and on which update it takes effect, including when a unit turns and when the change is abandoned part way through. A check that the game loads, or that the final image matches, is labelled as that and says nothing about the path in between.

The tests also cover what a second actor sees after a reservation or an update to a shared table, including while the first actor is between two of the points the spec marks with # visible: comments. Where the spec says a reservation survives a refresh of the table that holds it, or a save and restore, a test checks that. Where the spec says a side effect changes a route, a test checks that the route, and the timing the rules layer keeps for it, change along with what is drawn. A rejection by whatever requested the action is tested apart from a route that fails, and an object the rebuild keeps between updates is tested after the state it depends on has changed, such as which squares are occupied. A point the spec does not describe is a gap in the spec like any other, and the batch handles it as above: it adds the question and starts the row’s Notes with Spec gap:, then either leaves the row partial or writes the code with a PLACEHOLDER: comment. None of these tests is evidence from the original, so they do not bring a row to validated.

Code in src/ cites the rules, formats, screens and bugs it implements, and no findings or experiments. Before it picks any rows, an implementation session finds each citation of a finding or an experiment in src/ and settles it by what the code does. A citation of a superseded finding or experiment first moves to the entries its superseded_by names, as the research batch that superseded it should have done, and each of those is then settled the same way. Where the code implements one or more rules, formats, screens or bugs that are not superseded, the code stays. Code that implements a deviation that has not been dropped counts as implementing the entries its Departs from names. The citation moves to each of those entries that lists the finding or experiment in its evidence and that the comment does not cite already, and where there is none, the citation is deleted. A citation in a PLACEHOLDER: comment moves the same way. Each rule, format or screen that a citation moves to and that the comment did not cite before takes Code partial in its row, with a note naming the finding or experiment, so that an implementation batch checks the code against the entry, as a research batch does for code written for a superseded entry. Where no entry that is not superseded and no deviation that has not been dropped describes what the code does, the citation is the kind a research batch leaves when findings or experiments alone replace a superseded entry (see Research batches). The code was written for an entry that turned out to describe nothing, and the session removes it with the tests that exercise it. Apart from those rows, moving a citation changes an ID and nothing else.

An address of the original that a comment in src/ gives, as 0x… or as a neutral name such as fn_00478CD0, is backed by the entry the comment cites. The documentation check accepts it where that entry records the address, or where a finding or experiment in the entry’s evidence does, so the comment cites the rule, format, screen or bug the code implements and passes the check through that entry’s evidence. It does not cite a finding to pass the check, even where the check’s message suggests one. Where the evidence of no entry the code implements records the address, the comment leaves the address out. Code that needs such an address is a gap in the spec: the batch adds the question and the Spec gap: note as above and leaves the row partial without that code, since the check also fails an address used in code that the comments on its line and above it cannot back, PLACEHOLDER: comment or not.

This keeps the clean-room separation that the methodology asks for, and it is also a test of the spec. The standard’s aim is that someone could build a second engine from the documentation alone, and every implementation batch tries exactly that on a small scale. Under spec/ the batch only asks: it adds questions and unknown entries that hold nothing else, and it never adds evidence or changes a status or a description. With coding agents the separation costs little: an implementation batch runs in a fresh session, or a subagent, that is given the entry IDs and nothing from the research that produced them.

A feature that is not part of a parity row, such as online play, an optional improved AI or an accessibility setting, is ordinary engineering. It goes in implementation batches of its own, and a decision in docs/DECISIONS.md says why the project takes it on. It never counts towards a slice’s exit.

Calls that combine results

Where an entry describes a caller that decides its own result, its state and its later calls from what its callees return, the tests run the caller and its callees together through the consumer that uses the caller’s result, as well as each part alone. A function that decides from a child’s status whether to call a fallback can be right while the consumer that calls it passes on the wrong value, so a test of the function alone does not cover the consumer. The examples below are synthetic, and the standard says what the findings behind them record. Like the other tests of an implementation batch, these compare the rebuild with the spec and bring no row to validated.

Where the entry bounds a cycle in the call graph by a state field, such as a mode a stepper sets to 0 before it calls a restart routine that can call the stepper again, the tests start the cycle with the field set each way and check how deep the calls go and which path each call takes. Where the entry says a callee can set the field again, a test includes that callee. The stepper’s loop over its records gets tests of its own: records that end at once, records that end after several passes, and, where the entry says the loop can go round forever, a run under a step limit. A test that shows the depth is bounded shows nothing about the loop.

Where a caller calls callees in turn, a test lets an earlier callee report completion and a later one return 0, and checks that the caller returns what the entry says, the first event or the last result. Where the entry says the caller stops at the first event, the test also checks that no callee after the one that reported is called. A rebuild that stops at the first event agrees with one that keeps the last result whenever only the last callee reports anything, so a test where the last callee reports is not enough.

Take a caller that always returns 0 and calls a fallback only when its child returns exactly 1. The tests give the child the statuses 0, 1, 2 and a negative value, let the child change the output before it returns, and let the fallback change it as well. They check that the caller returns 0 each time, that the fallback runs only after the statuses the comparison takes for 1 and sees the child’s changes, and what the output holds at the end. Where the entry says the comparison uses fewer bits than the child returns, such as the low byte of a word, a further test gives a status that equals 1 only in those bits, such as 0x0101. A rebuild that passes the child’s status on passes only the test with 0, and one that falls back on every nonzero status passes the tests with 0 and 1 and fails the others. Several callers of one target can each test its result differently, so an adapter that gives the target one test of success for all its callers fails at least one of them, and the tests give each caller the statuses above.

Take a caller that calls one child for each plane of an image in turn and returns the bitwise OR of their UINT16 statuses. With children that return 3, 4 and 5 it returns 7, where keeping the first status gives 3 and keeping the last gives 5. A child that returns 0x8000 checks the high bit: where an adapter hands the result on as a signed 16-bit value, the test reads it back as UINT16 and expects 0x8000, and nothing on the way clamps the value or treats a negative one as a failure. Where a later child throws, the tests check that no child after it is called, that the effects of the children before it stay, and that the caller’s own caller receives the exception. The caller does not return the OR of the statuses so far, and no status value stands for the exception, unless the entry says the original catches it and returns one.

Take a validation caller that checks records in turn, stops at the first record that raises a warning, calls an emitter to report it and then returns success. Choosing a warning is not a failure return. The tests check that the caller returns success, and that no record after the warning is checked or admitted, including one whose boundary has not been read yet. The caller returns success only once the emitter completes, so a test lets the emitter fail and checks the caller’s result and state against the entry.

Two walks over one buffer can stop in different places. A copy that stops at an END marker can leave the rest of a newly allocated buffer holding whatever the allocator left there, and a later validation walk that runs to the number of boundaries minus one reads that rest. The tests put END early and make the bytes past END hold values that differ from zero and from anything the copy writes, through an allocator the test supplies or a buffer it passes in, since a rebuild whose allocator clears memory never shows what lies there. They check that the validation walk reads those bytes and does with them what the entry says. With one boundary the validation walk makes no passes and reads no record, so a test gives it a null record pointer and expects success. These tests run through the consumer that prepares the buffer and walks it, since decoded metadata on its own never reaches either walk.

A procedure can have more than one warning site, and the tests follow each one as the entry gives it, without carrying a stop or a failure return from one site to another. An earlier warning, such as one for an outdated format version, can call the emitter and, once the emitter completes, carry on into validation with the records still null, where the warning in the final loop stops. From the earlier site the tests compare a size mismatch found later, one boundary, which succeeds without reading a record, and several boundaries, where the walk reads through the null record pointer. Where the entry leaves the outcome of that read open, the rebuild stops at a PLACEHOLDER: comment as Implementation batches says, and the test checks that it stops there. Where the entry says the warning also closes a window, that effect does not settle what a later read of native memory, or of a file closed along with the window, returns, and the tests leave it as open as the entry does.

Rules behind an adapter

When a rule moves into the rules layer from a screen handler, a resource loader or any other part of the rebuild, the code that used it usually reaches it through an adapter, which converts that code’s types to the rules layer’s and back. A move can return the same values as before and still change what its callers see, so the tests of a move compare what the callers get and when, the order of the helper functions called, and which objects the callers own, as well as the values. Where there is an adapter, each case below runs against the rules layer directly and again through the adapter, because a correct rule behind an adapter that reads, validates or copies on its own still gives callers a different behaviour. Like the other tests of an implementation batch, these compare the rebuild with the spec and bring no row to validated.

The adapter keeps the contract its callers had for what it hands back. A collection they could only read stays read-only, and an object that several parts of the rebuild share stays the same object until a helper replaces it. The tests pass in a state object that no helper replaces and check that the caller gets back that object itself, then let a helper replace it and check that the caller gets the replacement, and check that each collection that was read-only before cannot be changed through what the adapter returns. An adapter that builds an equal copy at every helper boundary passes a test of values and fails this one.

An input the entry gives an outcome for belongs to the rule, even where it looks invalid, such as a negative index or one past the end of a table that the entry says ends a route. A check the adapter or a loader makes for safety, such as a bound on a file’s sizes, never rejects such an input before the rule sees it. The test for each such input checks the entry’s outcome through both paths, including the calls the entry says that branch does not make.

Where an entry describes an access outside an array, past its end or before its start, into the fields next to it, in a record whose layout a format entry gives, the rebuild models those fields as aliases of the slots outside the array. A collection that stops at its bounds, clamps the index or returns zero there behaves differently from the original. The tests give the array, the aliased fields and the destination of the access distinct values, so that a read from the wrong place shows, and they cover a write through one name followed by a read through the other, checking the value on each side of the write. Where no entry says what lies there, that is a gap in the spec, and the batch handles it as Implementation batches says.

A read the entry describes stays in the rebuild when the outcome no longer depends on its result, where the read itself can fail or change state. Take a rule that makes two checks and then reads a definition that may be invalid, where a value read earlier already decides the final result before that read. One test makes the definition invalid and lets both earlier checks pass, and expects the failure. Another makes an earlier check reject and expects the definition never to be read. Running both through the adapter catches an adapter that reads every definition up front as well as one that skips the read once the result is known.

Where a rule calls one of several targets, through a table or a pointer, and the spec has not read one target’s body, the rebuild does not treat that target as the same as one the spec describes, however likely that looks. Choosing the target is a function the tests can call by itself. The code stops at a PLACEHOLDER: comment citing the entry before the unread target changes any output or state, and the row’s Notes start with Spec gap:, as Implementation batches says. A test that passes because the code falls back to a described target’s behaviour tests the fallback, is named for it, and is never cited as showing that the two targets agree. The tests also check that the described targets still give what their entries say when one calls another.

Where the rebuild reads input in bounded pieces and hands each piece to a consumer that publishes effects as it goes, a failure appears at the point the entry gives for it. Unless the entry says the original checks later input before it acts on earlier input, a malformed piece later in the input never surfaces ahead of the effects or the failure of an earlier piece. The tests compare a failure in an earlier step with a failure in reading a later piece, and check that what the earlier pieces published is still there after the later failure. A problem found while reading the input is reported as a diagnostic, apart from the values the rule returns. A record can also be read in part: a step that follows a link to the next record needs only the link, and a step that draws from a record needs its other fields and not its link. Unless the entry says the original checks the whole record when it loads it, a field no step has used yet stays unknown and is checked when a step uses it, so a missing field never rejects the whole record early or reads as zero. Restoring a saved state follows a different contract, set out in Checkpoints and replay: it checks the checkpoint’s identity, which covers everything that decides how the game continues, and rejects a checkpoint that does not match before the host changes.

A failure partway through a rule keeps what the rule did before it, unless the entry says the original undoes it. Each kind of failure the adapter can meet has its own test, such as a parser or storage error and an error the rule raises for an input it cannot handle, since a boundary that handles one kind can let another escape. The tests compare the state set up before the failure, the writes helpers made and the output already published with what the entry says, and check that the source data is unchanged. A test that only expects an error to be raised does not cover this.

Arithmetic at the original’s widths

A rebuild written with wider integers, overflow checks or a different floating-point model can give the entry’s results for every ordinary input and part from it only at the edges of a type. Where an entry’s procedure wraps, truncates, converts between types or tests a value that it later changes, written as Types and arithmetic asks, the tests of its rule add inputs at those edges. Each test compares the branch taken, the stores, events and calls in the order they happen, and the exceptional outcomes the entry gives, under the assumptions the entry states about its inputs. Where there is an adapter, each case also runs through it, as Rules behind an adapter says. These tests compare the rebuild with the spec and bring no row to validated, and an input a test reaches shows nothing about whether the original’s callers pass it.

For each addition, subtraction, increment, decrement, multiplication or negation the entry says can wrap, a test sets an operand to the largest or smallest value of its type and follows the result through every test and store after it, as well as the value returned. Take a rule that compares z < limit + 1 in INT32 and then returns 0 without calling a probe when the comparison fails. With limit at the largest INT32, the test checks that the rule returns 0 for every z and never calls the probe. A rebuild that computes z <= limit, or does the sum in 64 bits, passes every ordinary input and fails this one. For a counter the entry decrements, stores and then sets to 0 when it is negative, the tests start it at 0, at 1 and at the smallest INT32, and check the value stored between the two writes as well as the last one. A rebuild that writes max(0, counter - 1) makes one store, so the negative value the original stores between the writes never appears, and if it subtracts in wider integers it keeps 0 from the smallest INT32 where the original keeps the largest.

A guard tests one value at one point. Where the entry tests a value and then transforms it, such as a roll checked for being positive before it is scaled and multiplied, a test picks a value that passes the check and comes out zero or negative after the transformation, and checks that everything after it happens as the entry says, the stores, draws and requested actions included. The rebuild adds no test of the transformed value that the entry does not give. Take a helper that returns at once when its UINT8 count is 0, and otherwise records a preparation, doubles the count, publishes the doubled length and requests half of it. The tests use 0, 1, 127, 128 and 255. A count of 128 passes the guard and doubles to 0, so the test checks that the preparation, a length of 0 and a request for 0 all happen, in that order, where a rebuild that tests the doubled value does none of them. Where the transformed count drives a loop that runs its body before it decrements and tests the count, a count the transformation takes to 0 wraps on the first decrement: a caller that skips a UINT16 count of 0 and a callee that halves it turn an input of 1 into 65536 passes, while 2 and 3 give one pass each. The tests use 0, 1, 2, 3 and 65535, call the callee directly with 0 as well as through the caller, and check the transformed count, the number of passes and the writes in order, each on its own. Where an operation makes requests or writes and its last count then comes out as 0, a test checks that those earlier requests and writes are still there afterwards, since a final count of 0, skipped or requested, undoes nothing that came before it. Where the entry gives two guards one after the other, each deciding an effect of its own, the tests cover each combination of their outcomes, including states where the first does nothing, such as a count of 0 or a timer already past its end, and check the stores in order. A rebuild that returns from inside the first guard skips the second, which a test with the first guard false and the second true shows. Where the entry says a step needs a value, such as a duration, a test leaves that value out and checks that the rule stops where the entry says, even when a later guard would pass.

The tests give each value the width the entry gives it where it is used. Zero-extending one operand changes nothing about another: a numerator whose high word the entry sets to 0, divided by a divisor the entry gives as INT32, is tested with the divisor’s sign bit set, since dividing by -2 and by 4294967294 give different quotients. With the divisor at -1, a numerator above 0x80000000 gives a quotient that does not fit in INT32, so that case is tested with the outcome the entry gives for it. A shift whose count the processor masks is tested with counts of 0 and 32, where a later test reads the count as it was stored. A rule whose caller tests or stores only a byte of the result is tested with different values in the upper bytes of the result and of the caller’s own storage, checking the bytes each write covers and each consumer at the width it reads, which is the case STATUS-11 and STATUS-32 describe for a reading. An adapter’s parameters and fields hold each value at the width the entry gives it or wider, and the rule takes each value at the entry’s width, keeping its low bits, so a wider field never carries a value into the rule that the original’s type could not hold. A test passes values outside the range of every narrower type, such as 70000 and -70000 for an INT32, and the type’s largest and smallest values, through the rules layer and through the adapter, and compares the two, through each wrapped sum, product, signed division and test of the low word that the entry gives.

Where the entry converts a value between types partway through, such as a difference of two UINT32 extents read as INT32 and divided by 2 toward zero to count 16-bit words, the tests cover an ordinary difference, a difference of -1, which gives 0 words, other small negative differences, such as -3, which give a negative count, and a difference that wraps round to a large positive value. They check what the consumer then reads and writes, including a path where it reads nothing. A count kept in one unit and a bound kept in another, such as a run length in pixels and a buffer bound in bytes, are tested with a format of more than one byte per pixel, where the two differ. An element that would cross the end of the extent the entry gives is tested on its own: it takes bytes from padding or the next block only where the entry says the original reads them, it is written in part only where the entry says so, and the byte after the last whole element keeps its value. A copy that fits its record can still run from one field into the next. Take an 8-byte record with a UINT16 length at offset 4, where the rule stores the character count plus 1 in the length and then copies the characters and a terminating 0 from offset 0. Three characters leave the length at 4, and four put the terminator on the length’s low byte, so the length ends at 0 where 5 was stored. Five characters still fit the record, put the fifth character on the length’s low byte and the terminator on its high byte, so the length ends as that character’s code. The tests use a count below that point, the count at it and the count above it, and check the length stored before the copy, every byte the copy writes including the terminator, and the field’s bytes afterwards. A check that the copy fits the whole record says nothing about the fields inside it. A 0 the entry stores as a value is tested apart from a code the entry treats as a skip.

Where the entry scales an index to a byte address after the index has wrapped, two indices can reach one address: two INT32 indices that differ by 0x80000000, scaled by 2 into a PTR32, address the same word. The tests write through one index and read through the other, as for any other alias. A fixture that holds a capture of memory keeps both indices that reached one address and the header words the capture observed, leaves out a word cut off at the end of the capture, and keeps a window that starts or ends inside a word apart from the whole words of the source. A limit on the index, such as a palette’s count, or the size of an allocation is never inferred from the range a capture covered.

Where the entry says a loop ends when a value reaches a cutoff, the tests use a value that meets the cutoff exactly as well as one that passes it, and give each axis of a scan over two axes the comparison the entry gives it, signed or unsigned. Where the entry says a scan can go round forever for some inputs, such as a width larger than the period it wraps at, the tests run the rule under a step limit with such an input twice: once with no cell that matches, where the test expects the limit to be reached, and once with a matching cell, where the scan ends at its first match. The entry names the bounds its inputs have to keep for the loop to end, and a test with small widths shows nothing about inputs outside them.

Where the entry reads a loop’s limit again on each pass and the loop’s own writes can reach the limit’s storage, the rebuild reads the limit at the same point and does not keep the first value. Take a 16-byte array, an 8-bit index that starts at 1, and a loop that writes 255 to byte (8 + 2 * index) & 15, increments the index and stops when the index is no longer below a byte limit of 5. The tests run it under a cap of 20 writes with two layouts. With the limit in byte 13, which no write reaches, the loop stops after four writes whether it reads the limit again or keeps it. With the limit in byte 12, the second write sets it to 255: a loop that reads it again reaches the cap, and one that kept 5 stops after four writes although memory now holds 255. The tests compare the writes in order, the limit each pass reads, and whether the loop ended or reached the cap. A count tested once before the loop does not bound the passes when the loop’s writes can change the limit it reads.

Where the entry stores a floating-point intermediate before a later step uses it, or uses a constant at single precision, the tests check the bits of each stored value as well as the result. FLOAT32(1) / FLOAT32(3) stored as FLOAT32 and then multiplied by FLOAT32(5) is 0x3FD55556, and the same chain in FLOAT64 narrowed once is 0x3FD55555, so a rebuild that computes the whole expression in double and narrows only the end result gets the last bit wrong. A constant is checked the same way: FLOAT32(5) * FLOAT32(0.01) is 0x3D4CCCCC, and 5 times 0.01 in FLOAT64 narrowed once is 0x3D4CCCCD. A value such as a radius that the entry stores as FLOAT32 is tested for the bits it holds after the store, and a later step that computes in wider precision starts from those bits. These tests run at the precision the entry’s Inputs section gives. A rebuild that computes in FLOAT64 and agrees with a model of its own computed the same way shows nothing about an original running with another x87 precision.

Where the entry converts a floating-point value through a helper that sets the rounding, produces a wider integer and restores the control word, the tests treat the width, the rounding and the exception state as three separate things. They start the helper under each rounding mode with fractional inputs, positive and negative, check the truncation, and check that the control word afterwards is the one it started with. They give the conversion a result whose high and low words differ and check that the caller uses the part the entry says, with no limiting to the narrower range. A NaN, such as the square root of a sum that wrapped to a negative value, and a value too large for the result are tested only where the entry gives their outcome under stated exception masks. Where it does not, the code stops at a PLACEHOLDER: comment citing the entry and the row’s Notes start with Spec gap:, as Implementation batches says.

Tooling batches

A tooling batch builds something the stages need that has no parity row of its own: the extractor, the Ghidra scripts, the emulator harness, the export of the function inventories, the scripts a live session measures with, the rebuild’s headless runner and the test harness that replays fixtures. It needs no decision and no approval, because this page already asks for it. A tool that reads the original belongs to the research side, and one that runs the rebuild to the implementation side, and each is built in a session of that side.

A tooling batch that adds or extends an analysis reporter checks the supported queries against the standard’s bounded analysis report contracts. It records the executable acceptance cases, unsupported cases and remaining limits separately from any changes to research guidance. A request for reporter behaviour that a game’s repository records stays open until the reporter has been checked against that request’s own case; adopting the guidance alone does not close it. The verification record names the request, case, source fingerprint, tool version, input assumptions, expected observation and outcome, and describes the case and observation in the researcher’s words. It says which results require a callee that has not been read to return or preserve state. Configurations and reports derived from the original stay in GAME_DIR, and a regression test added to the shared reporter uses synthetic instructions and state.

Allocation, containers and cleanup

Where an entry describes a rule that allocates a record and links it into a container, removes one, or releases what a structure holds, its tests run over synthetic state with the allocator and the release routine supplied by the test. A supplied routine records each call it receives and completes it the way the test says: it can fail, change state, call back, raise an exception or not return. These tests compare the rebuild with the spec and bring no row to validated, and a supplied routine shows nothing about the original’s allocator or the callees the entry has not read.

The allocator returns null. Where the entry says the code accesses the block through its base before any test of it, the test expects what the entry gives for that access (a fault, or a read or write at address zero, depending on the build), and checks that the rebuild does not report it through the error return of a later test. A test of a pointer derived from the base, such as the payload pointer, never stands in for a test of the base.

The allocator fills each block with a pattern before returning it. The tests check the requested size, the payload’s extent and the header’s extent against the entry each on its own, and check that the rebuild reads and writes nothing past the request that the entry does not say the original reads or writes, since the capacity past it is unknown. Where the rule fills the block by converting records from another layout, such as an older version of a file format, the pattern is still there after the conversion in every field the entry does not say is written, including the records a conversion that stops at an end marker never reaches. Where the entry says an earlier string copy can run past its field, a test gives a string long enough to do that and checks the bytes it changes past the field, and that a later copy into a named field changes that field alone. A test stops a conversion after part of the record has been copied and checks that the caller does not get the record as converted, and that the field holding its format or version has the value the entry gives at that point.

A count can be tested before the allocator is called and loaded again by the code that copies into the block. For example, a caller checks a 16-bit count in a header against a limit, allocates, stores the copy’s arguments and calls a helper that reads the count from the header itself. The copy runs on the count the helper loads, and the earlier test shows nothing about that value unless the entry shows that nothing writes the header between the two loads, as STATUS-38 says for a reading. One test makes the supplied allocator change the count after the check. Where the entry says one of the caller’s stores to its outgoing arguments reaches the header, another test puts a different value there through that store. Each checks that the copy uses the count from the second load. Where the entry decrements that count with wrapping before the loop, a count of 0 becomes the largest value of its type (65535 for the 16-bit count) and the copy runs on that instead of copying nothing. That is the edge Arithmetic at the original’s widths asks a test of for any decrement that can wrap, so the tests use counts of 0, 1 and the largest value of the count’s type, and, where the loop copies in chunks of a fixed size, a count that fills the last chunk exactly as well as one that leaves it part full. A count the caller’s check rejects reaches the copy the same way as any other changed count: the check passes on another value, and the allocator or the argument store then puts the tested count in the header. Each test checks the count the copy loaded, the number of iterations and bytes the loop writes, and the extents of the source and the destination, each on its own. Where the destination overlaps the source or the header, the test counts the writes the copy makes apart from the distinct bytes they leave written. A bound the tests check on the loop’s writes covers the writes the loop makes itself, and the tests assume nothing about what a callee the entry has not read writes.

An insertion is tested at each point the entry marks with a # visible: comment between its steps, such as after the header and payload are written, after the count is updated and before linking and balancing. A supplied routine called at one of those points sees the state the entry gives there. Where a retry, callback or exception handler can run between two steps that the entry does not separate with such a mark, that is a spec gap, handled as Implementation batches describes. Where the allocator can retry, call back or not return, each of those paths has a test that checks the rebuild keeps every earlier write and undoes nothing the entry does not undo.

A removal is tested with a release routine that reads the container when it is called and then changes the count. The test checks that the routine sees the node already unlinked and the count at its old value, that the removal decrements the count it loads at the point the entry gives (the count the routine left, where the entry loads it after the call), and that the iterator it publishes is the one it computed before the container changed. A rebuild that loads the count at another point, or treats the removal as one step, fails it. The tests cover a node with no child, a node with one child and a node replaced by its successor, and check which allocation each passes to the routine. Where the entry says the routine may raise an exception or not return, a test makes it do so and checks that no store the entry places after the call has happened.

Cleanup is tested in the order the entry gives. Where the entry releases and clears one field at a time, the test checks that each release request and the clear of its field come in the order the entry gives, whether the release comes first or the code saves the pointer and clears the field before releasing it, and that each completion answers the request with the same operation and pointer. A test whose routine writes into a field the cleanup has already cleared checks that the rebuild reads later targets from the structure as the routine left it, and keeps that write unless the entry clears the field again. Where the entry releases every reference before clearing any, a routine that reads the other fields finds them still set, and a value a routine writes stays until the store that clears its field. Two fields that hold one pointer give two requests, which the rebuild does not merge, and what the original does with a second release of one pointer is not a question these tests can answer. A cleanup over a list makes one request per element, which the tests keep apart from one call to a routine that releases the whole list.

What a cleanup leaves is the state the next read starts from, and once a routine has written to the structure it need not be all zeros. A test carries that state into the next read, through a read that finds no file, one that rejects the version and one that succeeds, and on to the consumer of the records the read produces. The consumer works from the state as the read left it and from the identity of the newly read data, including on the failure paths it takes after it has published anything.

Checkpoints and replay

A checkpoint or replay API in the rebuild states what its identity covers. A state fingerprint that a test compares with a recording covers only the fields that test compares. The identity of a save or a replay has to cover everything that decides how the game continues from it, so a fingerprint can serve as one only where it has been shown to cover all of that. The identity includes the resources that drive behaviour, such as motion banks, actor profiles, navigation costs and animation instructions, wherever they affect the next update of the game state, even when their filenames are unchanged. A map’s name and dimensions do not identify its content. The identity is a hash of named fields in a portable encoding, and the API states that encoding, its version, and whether a checkpoint with an older identity is migrated or rejected.

Tests change each of those inputs on its own, including fields a diagnostic export leaves out and resources changed under the same filename, and check that a checkpoint whose identity no longer matches is rejected without changing the host, the running instance of the rebuild that was asked to restore it. Passing them shows that the rebuild’s checkpoints agree with themselves. They say nothing about compatibility with the original’s save format, which needs format entries and rule evidence of its own.

The contract also says whether one snapshot can be restored several times with each copy run on its own, or must be restored into a fresh host. A snapshot record that never changes can still point at state that does, such as how far an animation has played, the open list of a path search that is still running, or the buffer that collects the sounds and messages an update emits. Continuation is deterministic only once each of those is restored or rebuilt as well. Where the rebuild deliberately leaves some state out of a save, such as a path search paused halfway, the contract says either that saving is refused while that state exists or how the state is rebuilt from what was saved, since the rules layer decides from the serializable game state alone. The state is never dropped without saying so.

Tests compare a run that carries on uninterrupted with one restored into a fresh host, from checkpoints taken just before and just after a point where the rules hand a result on, such as a unit arriving at the end of its path. Where an animation can advance while the unit stands still, its progress is part of the compared state. Tests also restore an older snapshot a second time: a contract that allows several copies keeps each copy’s effects apart, and one that allows a single line of play rejects the stale snapshot without advancing the host. They cover an order replaced or interrupted partway, and compare the effects emitted along the way as well as the final values. These tests compare the rebuild with itself, so they never bring a parity row to validated.

Continuation cases

The state that decides how play continues usually sits in several parts of the rebuild at once, such as a task record, a movement record, an actor and a path search that is still running. Each part’s own tests can pass while the parts together hand a stale or invented value on. Each game repository writes the continuation cases in this section over synthetic state for its own components, wherever the rebuild has the shape a case describes. They compare the rebuild with the spec and with itself, so they bring no row to validated, and a mocked callback that changes state says nothing about whether the original’s callee does.

Where two components each keep a copy of one field, such as an activity flag in a task record and in a movement record, or a profile flag copied into movement state, the contract names the component that owns the field and the points at which the other copies are updated from it. A test lets a helper change the field and checks that the next consumer sees the new value before the next helper call, and that a restore into a fresh host gives the same value. A checkpoint whose copies disagree is rejected unless it was taken between a change to the owner and the point at which the contract updates the copies, and a migration from an older format says which copy it takes. Where the rebuild keeps the direction a unit travels apart from the direction it faces, the tests change the facing on its own, including on a path that returns early or picks no new animation while a timer is running, and check that the direction of travel keeps its value. A field that says which of several kinds an object is follows the same rule, except that a copy kept as a record of the object before the caller changed its kind keeps the old kind.

Where a rule installs a handler through a setter and a later action dispatches through it, installing and dispatching are tested apart. A lookup that runs straight after the setter sees the new task and its dependencies, the setter does not call the handler, and a later action finds the installed handler and calls it. A callback that was requested and has not completed survives a restore, cannot be dropped by a projection or by the next operation, and completes only for the receiver and code it was saved with.

A checkpoint validator accepts every state the rules can publish, including one taken between two steps of a rule, where one field has moved on and another has not been cleared yet, such as a path cursor equal to the path’s length while the active flag is still set. It still rejects a value that no step produces, such as a cursor past the end. Where the rules decide something at one point and act on it at a later one, the saved state records whether the decision has been applied, since the other fields cannot always tell. Tests restore from each side of that point and check that every effect of the decision happens exactly once.

When a change lets a component publish a state it could not publish before, every component that keeps that state between updates is tested with it, as well as the save and the restore. A path search paused halfway is tested with its destination changed and a faster method then finding a route: the test checks that the paused work for the old destination is gone, although the route came from the faster method.

A checkpoint checks references held inside world data, such as an occupant recorded in a map cell, against the objects it saves, with the same guards the code that reads those cells uses, so that an empty-cell value or an unused word never counts as a reference. A reader rejects a checkpoint with a required field missing. The tests remove each required field on its own, including fields whose default value would pass every other check, and test an explicit null apart from a missing field, also through the readers a parent reader calls. The reader rejects null in a field that may not hold it, and a field that may hold null still has to be present. A round trip of what the writer produced never tests this, since the writer always writes every field.

Implementation batches already tests a kept object after the state it depends on has changed. Where a rule keeps an object it found, such as a target or one of several candidate positions, and reads that object’s own fields again after a call, a test lets the call change a field and checks that the next decision uses the new value while the kept object stays the same one. A rule that requests an action, such as damage or a move, is tested apart from the outcome: the test checks the request, and the outcome is whatever the code that carries out the action reports.

Where the entry takes a decision from a value read before a call, the rebuild does not take it again after the call. Where a rule keeps a reference to an object and an update then puts another object in that object’s slot, a later step that acts on the kept object, such as destroying it, acts on it and leaves the one now in the slot alone. Where the entry reads one value at several points, such as a range read once to choose a branch and again before each later draw, or a loop bound read again at the end of each pass, each read is tested on its own: a helper changes the value between reads, since a test that holds it constant passes a rebuild that reads it once.

A path that returns zero or fails can still have published state before it did, and the tests check that state as well as the result. A builder that moves a cursor before it checks whether its output is full is tested with a full output and an entry still to come, and the test checks the cursor as well as the output and what a restore or the next consumer sees. The end of the path, where the cursor does not move, has a test of its own. A loader that fails before it installs an image and one that fails after it has published references to the image can both return zero, so each failure point has its own test, which checks what stays installed and what its helpers changed against the entry. A rule that restores fields on the way out is tested for the order the entry gives. Where the entry says a helper that reports an error may not return, the tests cover both paths and check that the caller carries on only on the one where the helper returns.

What a failure leaves can also change the next call. Where a rule sets a flag saying it has tried, publishes some of its arguments and then calls an operation that can fail, a second call with that state kept can return zero at once without trying again, and a caller may publish its own completion on that zero. The tests fail the operation before and after the rule publishes a flag or handle saying it owns the resource, call the rule again with the state kept and with it changed, and compare the calls made, the arguments read, the status returned and what the caller publishes with the entry. A zero from the second call shows nothing about whether the operation succeeded. Where the entry gates cleanup on owning the resource, the tests keep owning it apart from having tried.

Where a component copies only the start of a record, such as the finished part of a route, while a search keeps its open list and workspace after that part, a checkpoint saves or rebuilds both, and the tests check what a restart reads, including any field just past the copied part.

A checkpoint keeps the resources the rules use now apart from the ones they have published, including the state behind a profile flag derived from them. When a resource changes, the components that choose a target, a waypoint and a cost from it are refreshed together, and the timing and reservations they keep change only where the entry says they do. A paused search keeps its live inputs apart from the state that can be saved. The writer records where each resource came from, or that it does not know, and a reader rejects a checkpoint that records neither.

The different value on each axis that Implementation batches asks for applies to identities as well. Where several integers identify one thing in different roles, such as the army that owns a unit, the slot it occupies in storage and its entity number, the tests give each role a different value in every component that produces, saves, reads or restores it: army 2, slot 4 and entity 7, since values that are all zero hide a swap. A checkpoint with two roles swapped is rejected before any dependency changes, and a migration that has to work one role out from another has its own test.

The identity of the source a request reads, such as a file path or a fingerprint of its content, is kept apart from the identity of the request and of the destination it fills. Two requests that load the same file with the same content into two slots are told apart by request and destination. A test hands each completion to the other request and checks that both are rejected before either destination changes, including a failed completion that keeps an image. A repeated load of one path and a completion for an owner that has since been replaced each have their own test.

A reader rejects input that names one thing twice before it builds anything from it: a member repeated in an object at any depth, numeric keys that parse to the same number, such as 11 and 011 read as decimal, and two policies with the same identity.

Where a field holds either a pointer to an object or a special marker value, the rebuild records a marker it recognised apart from a pointer it could not map, although neither leads to an object. Tests pass a marker, a null and an unmapped pointer through the consumer one at a time, including a branch that still accepts an ordinary pointer, and never infer the marker from a general flag for missing links. Where a format stores a field as an index and the loader replaces it with a pointer, index zero looks up slot zero and a null pointer is a different value, and the same bits can choose different branches before and after the replacement. The loader keeps the stored bytes apart from the object’s portable identity, and after the replacement the tests read the field through any other name that shares its bytes. They cover writes in sequence that change a later table entry, a repeated record’s next index or the first entry of an overlapping slot list, read the shared storage as it is now before any snapshot is taken, and cover addresses that wrap around. A field already turned into a pointer is never read as an index again, and an index that cannot be resolved becomes neither index zero nor a marker.

Commits

A commit message says what changed and what evidence it rests on, in plain words. It ends with trailers naming the entries the batch created or changed, so that the git history can be searched by ID:

Settle which gang rolls first in a mutual attack

The resolver calls the attacker's roll before the defender's, and a run of
200 attacks from a patched save agrees.

Spec: RULE-COMBAT-012, FND-COMBAT-031, EXP-COMBAT-009
Queue: Q-COMBAT-012

An implementation batch uses the same trailer for the entries it implemented. A batch that changes parity rows adds Parity: with the rows whose status it changed, and one that closes queue items adds Queue: with their IDs, so that an item, and the reading it tested, can still be found after the item is deleted.

Queue: lists the items the batch closed and no others. An item the batch worked on and left open, by adding Tried:, splitting it or moving it to another section or area, stays out of the trailer, and a batch that closes no item has no Queue: line. The open item is still in a queue file, and where the attempt recorded findings, its Tried: line and the batch’s Spec: trailer both name them. An earlier attempt at the same question ends like this:

Record the combat resolver's two roll calls

The resolver's entry makes both rolls, but an indirect call between them
has not been resolved, so the order is still open.

Spec: RULE-COMBAT-012, FND-COMBAT-030

Sessions

A session is one sitting of work, by a person or an agent. It may hold several batches, all of the same side: research and tooling that reads the original, or implementation and tooling that runs the rebuild.

A session’s handover is the handover section of the goal file it works under, or docs/HANDOVER.md when it works under no goal, so two sessions running at once never rewrite the same one. The commit that deletes a goal file adds what is still worth handing on to docs/HANDOVER.md and leaves the rest as it was, and a research batch that supersedes an entry moves its citations in docs/HANDOVER.md and the goal files (see Research batches). A session that finds either commit on the main branch when it ends, in the file that holds its handover, merges it before rewriting its handover, and keeps what a goal-deletion commit added unless it has dealt with it. Where the file it writes names an entry that has been superseded, it moves that citation as a research batch does.

At the start: read the session’s handover, and for a goal the rest of its file, check the branch and the working tree against what the handover says, and run the documentation check. A research session then triages open reports it may take, turns any Spec gap: note that has no queue item ID yet into a queue item, and picks the next item from its goal or the queue in the order above. An implementation session settles each citation of a finding or an experiment in src/ as Implementation batches says, fixes rows with a Defect note, then picks the next rows of the current slice from its goal or the plan, and opens neither the queue nor the files of research goals.

At the end: stop every process the session started (Ghidra, the original game, test hosts), leave processes that belong to anyone else alone, and release the run lock if the session holds it. Every finished batch has its commit already. Work left half done is finished, discarded, or left out of the batch commits and described under the handover’s unfinished work, since a commit that fails the documentation check or the fast gate, or holds two kinds of batch, is never made on the working branch. Where the working tree does not outlive the session, as in a cloud container, the half-done work is committed to a branch of its own, wip/ followed by the working branch’s name, and pushed there, and the handover names it. Then rewrite the handover to describe the state now and commit it on its own. Where the session’s goal was met or dropped, that commit deletes the goal file instead and moves what is still worth handing on to docs/HANDOVER.md (see Batches). Push the working branch afterwards, unless the repository’s AGENTS.md says the owner pushes or the person running the session says otherwise. Anyone who needs to know how far the branch is ahead of its remote asks Git, and the handover never records it.

Under a goal whose condition does not hold, the end of a session is a checkpoint and does not end the goal. Once the handover is committed, and the branch pushed where the session pushes it, the next session under the goal starts at once, in the same conversation, unless one of the reasons to stop in Coding agents and long-running goals applies.

Running the original

Runs of the original that an agent drives are the last resort for each question. They are fragile: an old game loses window focus, depends on timing, behaves differently under an emulator’s automation, and stops at a dialog nobody expected, and a result that cannot be repeated is not evidence. An emulated call starts no process of the game, so none of this section applies to it, and it comes before any run in the order of work. Runs of the game are kept to the questions nothing else can answer, and every agent run is scripted, starts from a fixed state, and records enough to be repeated, as Recorded runs describes. An item under Agent run or Live session is taken up only after a static reading of its own question has been tried and recorded under Tried:, or when it asks for the run that confirms a static reading of an entry that depends on something the code does not decide. A question that a static reading can settle is settled that way, even where a run could settle it too. Runs do not wait for the rest of the queue to be empty. They take their place in the order of work, where a run that blocks the current slice comes before static work that does not, and within one step of the order the static items come first.

Several agents usually work on different games on the same machine at once, under one account or several, and two runs at the same time take each other’s window focus, input, emulator or debugger. An agent therefore runs an original only while it holds the machine’s run lock. That is one file for the whole machine, shared by every game repository and every account on it: C:\ProgramData\refurbished-dinosaurs\run.lock on Windows and /var/tmp/refurbished-dinosaurs/run.lock elsewhere, unless the environment variable REFURBISHED_DINOSAURS_RUN_LOCK names another path, which the owner then sets the same way for every account. The owner creates the directory once, so that every account that runs sessions can create and delete files in it.

The agent takes the lock with an exclusive create, one that fails when the file already exists ([IO.File]::Open($path, 'CreateNew') in PowerShell, or a redirect after set -C in a POSIX shell), and never by checking for the file and then writing it. The file names the repository, the session, the time the lock was taken, and the process ID of every process the run starts, added as each one starts. If the create fails, the agent does not wait, and goes on with work that needs no run. It deletes the file when its run ends, and never deletes one it did not create, with one exception. A lock is abandoned when it was taken more than an hour ago and none of the processes it names is still running. Any agent may then clear it by renaming it to a name of its own, which only one agent can do, checking that the renamed file is still the lock it judged abandoned (and renaming it back if not), and deleting it. The handover says which lock it cleared. An abandoned lock the agent cannot clear, for example because another account owns the file, goes in the handover’s blockers for the owner.

Outside a live session, an agent never attaches to, sends input to or stops a process it did not start.

Recorded runs

In the Chaos Overlords restoration, a script called the probe starts the original under a debugger and plays new games for up to fifteen turns with nobody at the keyboard, and a test replays every run against the rebuild. The same rules apply wherever the runtime record says an agent can start the original without a person, read its memory and set breakpoints while it runs. In a live session the agent reads the process and never writes to it, so there the recording of draws and the copies of memory apply, and the memory writes and the replaced seed do not.

A run records the seed the game gives its random number generator and every call of the function that draws from it, each with the rule that made it, the bound it asked for and the result it got, as well as the state at the end. The probe finds the rule from the call instruction: the function inventory gives the function that holds it, and the rule entry that cites that function is the one the draw is recorded under, by its ID. A draw from a function no rule cites stops the recording, and the function gets an unknown entry and a queue item the way any function no entry cites does. The rebuild’s code cites the same rules, so each of its draws carries the rule ID too, and the test that replays the run compares the rebuild’s draws with the recording one by one, the rule as well as the bound and the result. When one differs, it names the first draw that differs by its position and the rule each side made it under. The failure speaks in entry IDs, which an implementation session may read, and no address of the original reaches a fixture or a test. That puts a divergence at one decision, where a comparison of the end state would only show that something went wrong earlier, and it catches differences that leave the end state alone, such as two rules that make the same draws in a different order. Where the rebuild reproduces the generator exactly, the comparison needs no tolerance: every draw and the generator’s position at the end agree, or the test fails. The rebuild’s generator takes the rule ID with every draw and offers a hook that lets a test observe each draw, and nothing else uses the hook.

A run reaches its state through breakpoints and memory writes wherever it can, because input is the part of a run that breaks. The settings a setup screen would commit are written at their addresses before the game reads them. A movie is ended by setting the flag its loop polls, since a click posted between two polls is lost. Options that stop play to wait for the player, such as warnings and detailed reports, are switched off in memory, and the game runs in a window so that it cannot take the screen. Where the run has to click, it clicks at coordinates the screen’s entry gives, and it waits for a state the probe can read, such as a counter reaching a value or a breakpoint in the loop that waits for input, and never for a fixed time or for a stretch with no draws. Every address the probe writes or watches is a format field, or a global whose glossary term gives evidence for its address rather than (unknown). Every field it writes is supported or established, the same as for a save patch, and the experiment’s Setup lists each write.

The probe can replace the seed as the game seeds its generator, so that a recorded run can be played again. A repeated run counts only once it has made the same draws as the recording, which the probe checks before anything else. When the rebuild diverges from a run, the run is repeated with its seed, and the probe copies the writable memory at the entry of the draw that differs, counted from zero as the test counts. The copy shows what the original held at that moment, and a static reading of the code that produced it shows why. The reading becomes the finding, and the values the copy showed go in the experiment’s Observations. The finding changes the entry it concerns, and an implementation batch then changes the rebuild to what the entry says. The rebuild is never fitted to the recording until the draws agree. A copy of memory can hold the game’s text and code, so it is never committed. It is kept in GAME_DIR/captures/, named by its hash, which the Observations give, and the fixture holds only numbers read from it by format field path or glossary name.

A run that passes shows little on its own. Each experiment has at least two runs with different seeds, and new experiments vary what decides which code runs: the settings a new game offers, the scenario, the seats given to people, and the length of the run. Long runs matter most, because they reach states that short ones never do. A list that keeps values from one call to the next, a table that fills up, and a loop without a bound that reads past the end of its array all showed up only after several turns. What such a read finds is part of the original’s behaviour, so the finding records what lies in memory after the array, the entry describes it, and the rebuild follows the entry. A read whose result would put the game in a state it cannot continue from, such as a unit on a map square that does not exist, is a candidate for a deviation, since the methodology lets the rebuild fix crashes and corrupted state.

An installed game can carry compatibility settings tied to its path, such as a demand for elevation. A copy of the executable in another directory escapes them, and links to the installation’s data directories let it still find its files. The settings the run does need are set for the child process alone. docs/RUNTIME.md gives the probe’s command line, and with the experiment’s Setup that is enough for anyone to repeat a run from its experiment entry.

Live sessions

Where the runtime record says a run needs a person, the evidence comes from a live session: the maintainer runs the original on their machine and plays to each point the script names, tells the agent when the game is there, and the agent takes its measurement against the running process (a memory read, a breakpoint, a dump of a structure, a frame capture) before the maintainer carries on. The agent holds the run lock for the whole session and adds the process’s ID to it. The process the maintainer started for the session is the one process the agent may attach to and read without having started it. It never sends that process input or stops it, since the maintainer plays. The maintainer’s time is the scarcest resource a restoration has, so a session is prepared so that it spends that time only on what needs a person.

A live session is requested, never assumed, and the request is a file in the repository, so that the owner can find it after the agent’s session has ended and plan a sitting around it. docs/live-sessions/NAME.md opens with a Status line (requested, accepted with the date agreed, or declined with the reason), then names the build and the machine it needs, the queue items it would settle by their IDs, the slices they block, and how long the session is expected to take, and then gives the script. The owner answers by changing the Status line. An agent does not ask again for a declined session without something the request did not have, such as a new item that blocks a slice.

An agent writes a request once the rules above let each Live session item it would settle be taken up, and when the items block a slice or are enough to fill a sitting. A request covers only items the goal writing it may take up (see Coding agents and long-running goals). Until the owner accepts, the items stay under Live session, the plan keeps the rows that need them at implemented, and work goes on with everything a live session does not block. An agent never waits idle for one.

The script lists steps. Each step gives the entries it concerns, how to reach the starting state (a save patch, or the choices on the way into a new game), the one input to vary, the moment the maintainer signals, what the agent measures then and in which form, and where captures go (GAME_DIR/captures/, named by hash). The measurements are written and tried beforehand as far as the original allows without a person, the addresses to read and the breakpoints to set come from the static readings, and steps from the same starting state follow each other. Nobody should need to ask a question during the session.

After the session, the request’s Status becomes held with the date. The recordings are turned into findings and experiments with fixtures, as the standard describes, in one research batch per area, and each batch deletes the items of its area that the recordings settle. The last of those batches deletes the request file. What the session showed about the tools, such as a measurement that a person has to trigger or one that works unattended after all, goes into the runtime record in the first of them.

Emulated calls

An emulated call runs one function of the original executable in a CPU emulator, on arguments and memory the experimenter chooses, and records what the function returns and what it writes. Nothing else of the game runs: there is no window, no timer, no input and no sound, so none of the fragility of the runs above applies. An emulated call starts no process of the game and needs no run lock, and any number of sessions can make them at once.

Any agent may build the harness and make emulated calls in every game repository. That holds where the runtime record says nobody can run the game, and where the repository’s AGENTS.md keeps agents from starting, controlling or attaching to the original or its emulator, since an emulated call does none of those things. A repository can narrow what this page lets agents do with the original running, and such a rule covers runs of the game only. Emulated calls need no decision from the owner. A call takes well under a millisecond, so one experiment can cover thousands of cases, and every run repeats exactly from its fixture.

The emulator is Unicorn, driven through its Python bindings. It emulates the CPU and nothing else, which for x86 covers 16-bit real mode, the 386 instructions that 16-bit compilers emitted, and 32-bit code, and it lets the harness stop at any address.

What an emulated call can settle

As evidence, an emulated call counts as a run of the original for everything the code decides. It checks what a function computes from its inputs: a formula, the order of its draws from the random number generator, its arithmetic at the edges of its types, the fields of a structure it builds, the bytes a decoder produces. It is recorded as an experiment, as the standard’s Experiments section describes, so it counts wherever the standard asks for a run of the original. An emulated call checks the body of a function and nothing around it. An entry reaches established through one only when the cases reach every branch the entry describes, and when the static reading of what lies outside the function is complete: every caller, every write to the state the function reads, and so what the entry’s When it runs and Inputs sections say. Until both hold, the entry stays supported, and its Open questions say which branches or callers are left. A rule row whose tests replay the fixture reaches validated. Every rule the code decides and whose functions the harness can reach therefore gets an Emulated call item once its reading is recorded, even when a complete reading has already established it, because that item’s fixture is what its row’s tests need.

It settles nothing the code does not decide. A rule whose procedure has a # may run: comment is never established or validated by emulated calls alone, since where the interruption falls is exactly what the harness does not model. The harness supplies the operating system, the interrupts, the hardware ports and the clock, so an entry that depends on them still needs an agent run, a live session or a capture. A write the harness sees at a video address or an I/O port shows that the function made the write, and nothing about the pixels, the sound or the timing that followed. It covers only the functions it calls: which callers reach a function, and with what state, is a question for the static reading or for a run of the whole game. Where the rules live in a script or bytecode that an interpreter in the executable runs, as in SCI or SCUMM games, calling the interpreter settles nothing about a rule. Overlay code, which the game loads while it runs, is out of reach as well.

A static reading of code that reads or writes I/O ports records each port access and where it falls on every path that reaches it. An emulated call can check which addresses a copy to video memory writes and how many bytes it copies, and what appears on the screen stays unconfirmed. A write to a VGA register can change what a later copy does to video memory, and a path that copies to a scratch buffer instead leaves the other paths as dependent on the hardware as before. The finding says which of the effects are CPU registers the function restores before it returns, which are the contents of a scratch buffer that other code shares, and which are state held by the device.

An experiment whose harness maps ordinary RAM where video memory would be, or whose stubs supply port values, names those substitutions in its Setup section and says which memory effects it compares. It also states what that comparison assumes about the slot, the segment, the byte count, the mask and any addresses that alias each other. Its fixture gives each port value a stub supplies by the glossary name of the value from outside the game it stands for, and the video memory it compares through the format entry for that memory’s layout, the same as any other memory. The experiment is evidence for those memory effects and nothing more. Whether the card holds valid data in its own memory, and the pixels that result, need evidence of their own, which neither a port value from a stub nor a copy that ran to the end can give.

The harness

Each game repository keeps its harness in tools/emu/, built in a tooling batch on the research side. The harness has a loader for the executable’s format (MZ, NE or PE) that checks the file’s hash against the build entry, maps its segments or sections, and applies the file’s own relocations. It has an explicit stub for each import, interrupt or port access that a function under test reaches. A PE loader fills each slot of the import address table with the stub for the import the file’s import tables name for that slot, read the way STATUS-42 describes. A slot that the tables give no import holds no stub, and a call through it stops the run with an error that names the instruction and the slot. An NE loader walks every relocation chain the way STATUS-44 describes and writes at each import site the part of the stub’s address that the record’s source type names: the whole far pointer, the segment word or the offset word, and for an additive record it adds the word stored there to the target’s offset first. The stub is the one for the module and ordinal or name the record gives, so a stub is keyed by what the file imports and does not depend on what an outside table calls it. A chain that the report cuts short or names as an error leaves sites the loader cannot find, so the loader stops with an error that names the segment and the record instead of running with chain links where far pointers belong. Any import, interrupt or port access without one stops the run with an error that names the instruction and the service or port, and for a port access whether it reads or writes and how wide it is. The value a CPU emulator returns from a port nobody modelled does not count as a stub. A stub does only what the call needs, such as handing out heap memory or reading a file, because each stub is our model of the operating system or the hardware and adds nothing from the original. A function that reaches the screen, sound, input or the timer cannot be settled by an emulated call.

The harness is committed. It reads the executable from GAME_DIR the way a test does, and holds no bytes, disassembly or strings from it. docs/RUNTIME.md records the Unicorn version, the builds the harness loads, and the stubs it has, including any port models and the limits of what they test.

Protocol

An item under Emulated call names the function a static finding has located and the entries whose reading it tests. One research batch settles it:

  1. It states the reading under test from the entry’s description, or the competing readings from its Open questions, each written in tools/emu/ as a procedure that takes the same inputs as the function.
  2. It sets up the state the function reads: its arguments, the globals it reads, such as the generator’s state, and the structures it follows pointers into. The harness starts from the memory the executable initialises when it loads and changes only what the experiment needs. A structure is written through its format entry’s layout table, and every field written must be supported or established, the same as for a save patch. The fixture gives the arguments by the names in the rule’s Parameters section and the memory by format field path or glossary name, never by register, stack offset or address. The harness does the translation to the original’s calling convention and layout, so that a test of the rebuild can replay the fixture without knowing either.
  3. It chooses the cases: the values the reading treats specially, the edges of each type (zero, one, negative values, the largest signed and unsigned values, and ranges that overflow the width the code computes in), inputs that take every branch the entry describes, and random cases drawn from a seed the experiment records. The harness records the instructions each run executed, and the experiment gives the share of the function’s branches its cases reached. A branch no case reached is not covered, and the entry’s Open questions say so.
  4. It runs every case in the emulator and in each reading, and compares the results exactly. A reading that disagrees on any case is ruled out, and the case goes in the Alternatives section of the finding that rules it out.
  5. It records the experiment and moves the entries along the life of a claim: established where the one reading left agrees on every case, the cases reached every branch the entry describes, and the reading of its callers and inputs is complete; supported with the gap in Open questions where any of those is missing; disputed where the results contradict its description.

When a result looks wrong, or disputes a reading that looked complete, the batch may confirm it by replaying the same cases in Ghidra’s p-code emulator, which loads the executable and runs the CPU with code of its own, and recording in the experiment whether the two emulators agree. When they disagree, the harness or an emulator has a defect, and nothing in the spec changes until it is fixed. The item moves to Blocked with Waiting on: naming the defect and the case that shows it, and a tooling batch fixes the harness or records the emulator’s defect in docs/RUNTIME.md.

A function whose results do not settle an entry, because a stub or a structure the harness would need is missing, keeps its item, with Tried: naming what was missing.

Reports from testing

People play the rebuild and report what does not work the way they expect, in words and with screenshots. They do it when they happen to, and nobody schedules it. An agent makes no assumption about when a report arrives or whether one ever will: it never waits for one, never makes testing a step of its own work, and never counts a row as checked because someone might play it. It handles a report when one arrives and otherwise keeps working through the queue and the slices.

A report is recorded at once, by whichever session receives it and on either side of the clean room, as docs/reports/R-NNN.md, numbered from the Next ID: line in docs/reports/README.md the way queue items are. The file gives who reported it and when, the rebuild’s commit or release they played, what they said in their own words, the steps to get there if they gave them, and each screenshot by its hash. Screenshots show the game’s images, so none is committed. They go into the local reference store below, and the report cites them by hash.

Triage compares the rebuild with the original, so it is research-side work. A research session triages open reports before it picks a queue item, and each report takes one research batch. The session is one whose goal claims the areas the report concerns, or one under no goal. Triage decides which of these the report is, and the batch deletes the report file once its content is somewhere else:

  • The rebuild does not do what the spec says, such as a crash, a value an entry gives, or a screen that differs from a capture the spec records. The row goes back to Code partial if it was complete, and its Notes start with Defect (R-012): and what the rebuild does against what the entry says, in the spec’s terms only. An implementation session takes rows with a Defect note before other rows, adds a test that fails without the fix, and removes the note.
  • The spec is silent, or the report suggests the original does something else. The entry gets an open question, or an unknown entry is created, and a queue item names the report’s ID. What a tester remembers of the original is a lead to check, not evidence.
  • The report comes with a screenshot or recording of the original, of a known build, and says how the game got to that point. That is a dynamic finding, recorded with the capture’s hash under the standard’s rules for captures. It is checked against the Live session and Agent run items it could settle as well as the report’s own question. A tester’s screenshot is rarely taken at the canvas size with scaling and filtering off, so it can show what is on the screen but is not compared pixel for pixel.
  • The rebuild does what a deviation that is switched on says. Nothing changes, and the reply names the deviation.
  • The report does not say enough to tell. It stays open, with a Missing: line saying what would decide it, and is taken up again only when more arrives. The work goes on without it.

The session that receives a report answers the reporter with what happened, naming the IDs. A commit that settles one ends with a Report: trailer, so its history can be found by the report’s ID. An implementation session that receives a report directly acts on it only as far as the spec already says what the rebuild should do, and records everything else for research. It may look at the tester’s screenshots of the rebuild, never at screenshots of the original.

Screenshots of the original are what a report is compared with, so the project keeps a durable local store of them. It is GAME_DIR/captures/, where the standard already keeps the captures and saves that cannot be committed, each named by its hash. It lives with the maintainer’s copy of the game, is backed up with it, and loses nothing when a report or item closes, since findings cite captures by hash. A local index in the store, GAME_DIR/captures/index.tsv, gives each capture’s hash, build, the screen entry it shows and the state it was taken in, so that the capture for a reported screen can be found. The index is not committed, since its descriptions of states can hold the game’s text. Screenshots of the rebuild from reports go in GAME_DIR/reports/, named the same way. Where the store has no capture of a screen a report concerns, the queue item for it says which capture would settle it.

Measuring progress

Progress is measured by numbers a script computes from the repository, never by numbers someone writes down:

  • the parity totals in PARITY.md, by Status and by Code;
  • the spec’s entries by status, from the status index;
  • for each analysed file, the share of its functions, and of its bytes, that some entry cites, measured against its function inventory;
  • the share of the manifest’s data files whose format entry is supported or higher;
  • the number of queue items of each kind of evidence;
  • the number of open reports.

A function inventory lists the functions of one file the analysis reads: the executable that runs the rules, and each DLL, overlay or other file of code the analysis goes into. It is coverage/ at the root of the game repository, then the build’s ID, then the file’s path as the manifest writes it with .tsv added: coverage/BLD-GOG-EN-1.1/Chaos Overlords.exe.tsv. A file read from the disc keeps its path on the disc, and its CD: or CD2: prefix becomes a directory named @CD or @CD2, since Windows allows no colon in a file name: coverage/BLD-CD-EN-1.0/@CD/GAME.EXE.tsv. A member of an archive is inventoried under the path the standard’s ENTRY-TYPES-21 gives: coverage/BLD-CD-EN-1.0/@CD/INSTALL.LIB@/SETUP2.EXE.tsv. A script in tools/ exports it from the analysis database, which stays on the researcher’s machine as the methodology requires, and exports it again whenever the analysis finds functions it had missed or merged.

Each line gives a function’s start address, written the way the standard’s notation writes one for that file, its size in bytes, and optionally a name the researcher gave it and the reason it is out of scope. When the body is not one range that starts at that address, a further column lists its ranges as start and end address pairs, and the size is their total. That is a catalogue of where the code is and how much of it there is, and it reproduces none of it, the way a list of a film’s scenes with their timestamps shows none of the film. It never holds decompiled code, disassembly, bytes or byte signatures, strings or constants from the original, or names that came from the original, such as debug symbols, RTTI class names or export tables. A function that got its name from one of those is recorded under a name of the researcher’s own, or with none. The template’s AGENTS.md forbids committing broad decompiler exports, and an inventory that holds only these columns is the one export that is committed. The coverage script reads only the spec and the files in coverage/.

Two files sit beside each inventory, named after it with .provenance.tsv and .regions.tsv in place of .tsv. Chaos Overlords.exe.provenance.tsv says where the inventory came from, one name and value per line: the file’s xxh3, the analysis tool and its version, an identifier for the database snapshot the export read, and the export script’s revision. Chaos Overlords.exe.regions.tsv holds the totals of the denominator audit: one line per executable region measured, with its start address, its size, and for decoded instructions, defined data and undefined bytes, the total and the part outside recognised bodies. Both hold only addresses and counts, and the same script writes all three files.

Coverage counts what the inventories list, with the functions they exclude named as excluded, and is never a share of the behaviour understood. A function whose bytes an entry cites has been looked at, not read completely. Only an entry that lists findings in complete_reading says a function was read completely.

A test count is not progress, and neither are lines of code or the number of assets the importer extracts. A percentage of completion is taken from the parity matrix or not given at all.

Establishing an early baseline

Set the builds, code files and behavioural scope before computing coverage. Reconcile the build manifests with what the executable loads and what the research reads. List files without inventories and explain exclusions, including files of a supported build that has not yet been analysed. Imported libraries and code generated at run time go on that list too: their absence from the main executable’s inventory does not put them outside scope.

An initial report can be useful before any function has been completely read. Give each file its mapping baseline, the inventory revision and the unassigned ranges from the denominator audit. Use the spec’s stable areas to organise research, keeping functions whose area is unresolved in an unassigned group. Linking a function to an area requires evidence about its role. An address mentioned in an area’s prose is a lead to check, and does not by itself place the function in that area. Validate address joins against the file, build and address notation, and use actual body ranges when a citation names an interior instruction. Neither a start-plus-size interval nor a text search alone is a reliable coverage join.

A function counts as completely read when the static findings that an entry lists in complete_reading read all of it, under the conditions of STATUS-4. The inventory gets no completion flag of its own. A function linked to several entries counts only when their findings together cover the whole function. Where that evidence cannot be checked, the complete-reading count is reported as unavailable; where a checked baseline contains no qualifying functions, it is zero against that baseline. The number of established entries is never reported in its place.

Compare later reports on both their previous and current inventories. Show discoveries, removed or merged definitions and changed boundaries separately from new research evidence. A growing denominator can lower a coverage share even though research has advanced. Check the comparison with controlled additions, removals and boundary changes that leave research evidence unchanged. Counts cannot show whether an unresearched controller matters more than several small helpers, so each area also names its unresolved behaviours and dependencies. Spec status, queue questions and parity rows remain a separate view of behavioural completeness; they cannot prove the list of behaviours is exhaustive.

Creating and checking an inventory

Export from the validated analysis database for the exact build and file in the manifest. Record the executable’s hash, the analysis tool’s version, the database snapshot and the export script’s revision in the inventory’s provenance file. Open the database read-only with automatic analysis disabled, so measuring it does not change the functions being measured.

Enumerate internal function definitions in address order. Use each function’s complete body address set to count its bytes. A body can have several separate ranges, and adding its byte count to its entry address does not give its end. Keep thunks, library routines and unresolved definitions in the initial inventory. An external import with no body in this file contributes no function body here; a DLL the research reads gets its own inventory. An exclusion needs a recorded scope reason, rather than an assumption that a compiler-generated routine cannot matter.

Check that entries are unique and ordered, that each nonempty body contains its entry, and that every exported entry and size agrees with the database. Report definitions with no decoded instruction at their entry as anomalies to investigate; silently dropping them would hide uncertainty. Preserve researcher names and exclusion reasons by entry address when refreshing. If an annotated definition disappears after functions are split or merged, reconcile it before publishing the replacement.

Write to a temporary file and replace the previous inventory only after validation succeeds. Require the script’s completion marker and a valid output file as well as a successful process exit: a headless tool can exit successfully after failing to load its export script. Independently check the table’s structure, row count and byte totals. Export the same snapshot twice and check that the two files are identical. Commit the inventory with its provenance and regions files; diagnostic reports that include original symbols, instructions or data remain local.

Checking the denominator

The inventory’s total is the number of functions currently recognised by the database. Check how much code it leaves unassigned before using that total to describe relative progress. Form the union of function body ranges and intersect it with the executable regions for the file, recording which regions and loaded or file-backed bytes were measured. Separately report bodies outside those regions. The size of the executable regions is an upper bound on the code, since those regions can also hold data and padding.

Partition the executable regions into decoded instruction bytes, defined data bytes and undefined bytes, using the database’s current interpretation. For each class, report the bytes outside recognised bodies as well as the total. Check that the classes do not overlap and that their totals equal the measured region size, then write the totals to the regions file, so mapping coverage can be computed from the repository without the database. Decoded instructions outside bodies are concrete leads for missing functions or boundary errors. Undefined bytes require investigation before being called code, data or padding; even decoded instructions can turn out to be data.

Follow entry points and references, including indirect calls and address tables, to investigate those leads. Record unresolved ranges and the questions that would classify them. Function discovery changes the inventory, which is why every report names its inventory revision. The denominator remains provisional while unresolved code and boundaries remain.

Reporting research coverage

Report coverage by spec area, with an unassigned group and named research gaps, rather than combining function counts into one completion percentage. Give function counts and unique body bytes together, so many small helpers do not outweigh an unexplored controller. A function can concern several areas; show that relationship without counting the function or shared body bytes twice in the aggregate. List exclusions and show totals before and after them.

Keep mapping coverage, citation coverage and complete reading separate. Mapping coverage measures recognised bodies against the measured executable regions, with the unresolved bytes alongside it. Citation coverage measures which recognised functions the spec touches. The complete-reading count follows the rule in the early baseline: an established entry that cites an address does not mark every function it touches as completely read. These figures measure research over the current inventory; behaviour and rebuild completion still depend on the spec, open questions and parity matrix.

What needs the owner

The owner decides eligibility and the supported editions, the scope and the non-goals, any deviation whose default is on or mandatory, any feature outside the parity matrix such as online play, when a release goes out, and whether and when live sessions happen, which they answer in the request files. Emulated calls and the harness that makes them need no decision, even where the owner keeps agents from running the original. Anything else goes ahead without approval, and the owner reviews the result. A question only the owner can answer goes in the plan’s owner questions, and the work that depends on it waits under Blocked in the queue.

Coding agents and long-running goals

A long-running goal, such as Claude Code’s /goal, keeps an agent working until a condition holds. In Claude Code a second model judges that condition after every turn from what the conversation shows, without running anything itself. A good condition therefore names a state the agent can demonstrate by running something, has a scope, and has a limit. For a goal with a bounded scope the limit is a number of turns:

Every item under Static in queue/COMBAT.md is closed or moved to Blocked with
what was tried, the documentation check passes on the last commit, and each
batch ended with a status block; or stop after 40 turns.
Every parity row that slice 3 of docs/IMPLEMENTATION-PLAN.md names has Code
complete, or is partial with a Spec gap note saying what the spec is missing, the
fast validation gate and the documentation check pass on the last commit, and
the only changes under spec/ are added open questions and unknown entries; or
stop after 60 turns.

A goal as broad as “finish the combat system” gives the judge nothing to check and the agent no reason to stop. The goal’s file in docs/goals/ holds the same condition with its scope and the areas it must not touch, so a second session can see that the areas are taken.

A standing goal runs until the restoration ends, and that end is its limit. Its condition names the state the Audit stage ends in, such as every queue item closed or accepted by the owner as out of reach, or every parity row validated, deviated, or with a note saying why it can be neither, and gives no number of turns. Work that is only blocked for now does not meet it. The reasons to stop listed below take the place of the turn limit:

Every queue item is closed or is one the owner has accepted as out of reach,
the documentation check passes on the last commit, and each batch ended with a
status block; stop earlier only for a reason the work protocol's goal section
lists.
Every parity row is validated or deviated, or has a note other than a Spec gap
note saying why it can be neither, the fast validation gate and the
documentation check pass on the last commit, and the only changes under spec/
are added open questions and unknown entries; stop earlier only for a reason
the work protocol's goal section lists.

A goal stays on one side of the clean room, as a session does: research and the tooling that reads the original, or implementation and the tooling that runs the rebuild. A restoration that should run to its end therefore takes two standing goals, one for research and one for implementation, each in its own conversation and worktree, and both may claim the same areas when their goal files name their sides (see below). A standing goal that stops with work left, such as an implementation goal whose remaining rows all wait on research, keeps its file until the owner or a scheduled task starts it again. Where no session can push to the main branch, only one goal runs at a time, and a goal/ branch with its goal file at the tip blocks every other goal even while its agent is stopped. The two goals there take turns: a standing goal that stops with work left ends as a dropped goal does, deleting its goal file and moving its handover to docs/HANDOVER.md, and its side’s next turn starts a new goal with the same condition.

Apart from its condition holding, an agent under a goal stops only for one of these reasons, and the handover of its last session names the reason:

  • the owner asked it to wrap up and the wrap-up below is done, or the owner told it to stop;
  • every item in the goal’s scope is under Blocked, or needs a person or a run that cannot happen now, and planning adds no item it can take;
  • a decision only the owner can make blocks all the work left in the goal’s scope;
  • for a bounded goal, it has reached its turn limit.

A finished batch, a printed status block, a committed handover, a long conversation, a check running in the background and a question from the owner are not reasons to stop. The agent answers the question and goes on, and it takes up other work while the check runs.

When the owner asks an agent to wrap up while work in the goal’s scope is left, it starts no new item and no new agent, and ends the session as Sessions says, with a handover that records the stop at the owner’s request. The goal file stays, except where the two standing goals take turns on goal/ branches as above, and a later session resumes the goal. The request to wrap up authorizes a push of the work to the main branch, unless it says otherwise or the repository’s AGENTS.md says the owner pushes. Where work lands through pull requests, the agent pushes its working branch and opens or updates its pull request instead, and a session without the access to push to the main branch pushes its working branch. Then the agent stops.

Every batch ends by printing a status block, which is the evidence the judge reads and a summary the next person can use:

Status
Goal: docs/goals/combat-static.md
Batch: research, RULE-COMBAT-012 supported -> established
Queue COMBAT: static 3, emulated call 2, agent run 1, live session 4, source 0, blocked 2
Checks: documentation check passed, fast gate passed
Commit: 3f2a9c1 (not pushed)
Next: Q-COMBAT-015, RULE-COMBAT-014, the retaliation roll

The template ships the protocol’s recurring procedures as agent skills in .claude/skills/, one per procedure: runtime-access checks what can be done with the original running, plan-work plans and seeds the queue, start-session and end-session open and close a session, research-item and implement-rows carry out a research or implementation batch, triage-report records and triages a report from testing, and live-session requests, prepares and ingests a live session. A skill holds the steps and points to this page and the standard for the rules. Where a skill and this page disagree, this page applies, and the skill is fixed.

Agents read this page, the methodology and the documentation standard from a copy in the repository, not from this site. The template keeps them in docs/upstream/, byte for byte as published, with the commit they came from and their hashes, and every link to them in the repository points at the copy. A link to a section gives the section’s lines, and an agent reads those lines rather than the whole page, and never reads a section again once it has it. Agents assume the copy is current and do not go online to check. When these pages change, a person refreshes the copy, and only then.

Several agents can work on one game at once when each takes different areas. A goal file claims its areas, and the batches of one goal change only those areas’ entries, queue files, parity rows and deviations, except for the citations in other areas that a research batch moves as Research batches describes. A goal takes up a queue item only if every entry the item names is in an area it claims. It may add an area to its claim, in its goal file, while no other goal file claims that area. Here and below, an area another goal claims means one claimed by a goal on the same side of the clean room, or by a goal whose file does not name its side, so a research goal and an implementation goal whose files name their sides may claim the same area. Every session has to see the claims, so where every session can push to the main branch, a goal file, and every change to its claim, reaches the main branch before the first batch that relies on it. Where work lands through pull requests, that commit goes in a pull request of its own and is merged before any batch that relies on it. Each agent works in its own worktree or branch, and writes its state only in its own goal file’s handover.

Where no session can push to the main branch, because the sessions lack the access or because the owner’s instructions forbid it, only one goal runs at a time, and its claim lives in the one clone that every session’s worktree is made from. The goal works on a branch named goal/ followed by its file’s name without .md, and the first commit on that branch creates the goal file. Worktrees of one clone share its branches, so at the start of every session, and again before it starts or resumes a goal, a session runs git branch --list 'goal/*' and looks in docs/goals/ at the tip of each branch listed. A branch whose tip still has its goal file is the running goal: a session resuming that goal continues on it, and no other goal starts while it is there. A session that starts a goal runs the listing once more after that first commit, and if another branch listed has its goal file at the tip, it deletes its own branch and starts no goal, so two sessions that start goals at the same moment cannot both go on. The branch holds the claim, and a copy of the goal file that reaches the main branch because the owner merged the branch claims nothing there. Deleting the goal file on that branch ends the claim, whether or not the owner has merged the branch yet, and the owner’s next merge of the branch removes the copy. A separate clone or a cloud container sees none of this, even one that has fetched the branch, since git branch --list shows only the clone’s own branches and a fetch adds remote-tracking ones. A session that does not run in a worktree of the shared clone starts no goal unless the person running it says that none is running. In this example the owner pushes and agents never do:

~/chaos                 the shared clone, on main
~/chaos-wt/combat       worktree on goal/combat-static, docs/goals/combat-static.md at its tip
~/chaos-wt/economy      worktree on economy-survey, a session under no goal

$ git branch --list 'goal/*'
  goal/combat-static

A session that resumes the combat goal finds goal/combat-static this way and continues in the worktree that has it checked out. A session in the economy worktree that is asked to start a goal for the economy areas finds the same branch with its goal file at the tip, so it does not start one, even though the two goals would claim different areas, since only one goal runs while the claims cannot reach the main branch.

Where only some sessions may push to the main branch, as when the owner authorizes a push in some tasks and forbids it in others, a goal takes the form the session that starts it can use, and keeps that form until it ends. A goal started in a task that authorizes the push has its file on the main branch. A goal started without that authorization runs on a goal/ branch, as above, and is the only goal on a branch while it runs. A goal file on the main branch claims its areas for every session, whether or not that session may push, unless the shared clone has a goal/ branch of the same name: the file is then that branch’s copy, and the branch’s tip decides. A goal started on the main branch therefore never takes the name of a goal/ branch in the shared clone, and a goal/ branch is kept until the main branch no longer has a copy of its goal file. A session that resumes a goal under a different authorization from the one that started it goes on in the goal’s form. A goal on a branch stays on its branch when the task authorizes a push, and pushing the branch does not move the claim. A goal on the main branch keeps its claim there when the session cannot push, but that session cannot change the claim, so it takes up only items in the areas already claimed, and the claim ends when a commit that deletes the goal file reaches the main branch. Its batches and the handover it rewrites in the goal file stay on the goal’s working branch until they reach the main branch, so the next session under the goal continues on that branch and reads the handover there, since the copy on the main branch is older.

In such a repository a session checks both forms before it starts a goal or adds an area to one: it fetches the main branch and reads docs/goals/ at the remote’s tip, and it runs the goal/* listing. On a main branch without docs/goals/, git ls-tree exits with “Not a valid object name”, which means no goal file claims anything there. A goal on the main branch may claim only areas that no goal file on the main branch and no running goal/ branch claims. A goal on a branch may start only while no other goal/ branch is running, and may claim only areas that no goal file on the main branch claims. After its claim commit reaches where the claim lives, the session checks both forms again. For a goal on a branch that is the first commit on the branch. For a goal on the main branch it is the push to the main branch, or, where work lands through pull requests, the merge of the claim’s own pull request, and until that merge the goal claims nothing and runs no batch. If another goal now claims one of its areas, or for a goal on a branch another goal/ branch is running, it withdraws: it deletes its branch, or lands a commit on the main branch that deletes its goal file. A session that added an area checks both forms again after that commit in the same way, and on a clash withdraws only the area, with a commit that takes it out of the goal file. A claim on a branch can be seen only in the shared clone, so a session outside it starts no goal of either form, and adds no area to one, unless the person running it says that no goal/ branch is running. In this example the combat goal started in a task that authorized a push, and the session in the economy worktree has no such authorization:

~/chaos                 the shared clone, on main
~/chaos-wt/combat       worktree on combat-static, under docs/goals/combat-static.md
~/chaos-wt/economy      worktree on economy-survey, a session under no goal

$ git fetch origin
$ git ls-tree --name-only origin/main:docs/goals/
combat-static.md
$ git branch --list 'goal/*'

The combat goal’s file reached the main branch by the authorized push, and no goal/ branch has its name, so it claims the combat areas for every session. Asked to start a goal for the economy areas, the economy session finds them claimed in neither form, so it creates goal/economy-prices with the goal file as its first commit, checks both forms again, and goes on. Asked instead to start a goal for the combat areas, it finds them claimed by the file on the main branch and starts no goal. While the economy goal runs, a third session in a worktree of the shared clone starts no goal on a branch, since goal/economy-prices is running, but in a task that authorizes a push it may start one on the main branch for areas neither goal claims. A session that resumes the economy goal in a task that authorizes a push goes on with goal/economy-prices.

PARITY.md and the files in spec/index/ change with almost every batch, and the documentation check writes them. A merge conflict in them is never resolved by hand: take either side and run the check, which writes them again from the merged spec and parity rows. A repository where they conflict often can update them on the main branch only, as the standard’s Where it lives section describes. The check then writes them only on the main branch, a batch never changes them, and the main branch’s copies are as old as the last run there. Two branches that create the same spec ID, deviation ID or queue item ID are handled the way the standard’s Identifiers section says: the one merged second renumbers.

Versions

This is version 1 of the protocol. A change that would make a repository that follows this version stop following it gets a new version number, listed here with what to change.

VersionChangesConverting a repository
1The first version.Write docs/RUNTIME.md, move open research questions from the plan and handover into queue/, cut the handover down to the current state, and move dated checkpoints out of the plan (git keeps them).

Get told when a dinosaur wakes up

A few emails a year: new restorations, playable releases, nothing else. Unsubscribe link in every message.

The mailing list is not wired up yet. Until it is, the RSS feed carries the same announcements.