Local workspace¶
Coney's tools expect a small set of folders next to the repository: your game files, a PCSX2 install, Ghidra, a
scratch area and any extra worktrees. This page describes that layout, how to create it and how to tell the tools
where things are. None of it is published or checked in; you can arrange your machine differently as long as
coney.local.toml points at the right places.
The layout¶
Coney/ the workspace (a plain folder, not a repository)
repos/coney/ the main checkout; agent sessions start here
worktrees/coney/<branch>/ every worktree, and nowhere else
scratch/ captures, dumps, tool output, leftovers; never committed
cache/ccache/ the compiler cache every checkout's builds share (building.md#compiler-cache)
cache/deps/<version>/ the fetched dependency sources they share (building.md#shared-deps)
game/ your disc image and files extracted from it
emulators/pcsx2, pcsx2-b, ... portable PCSX2 copies of the PCSX2-MCP fork build (plus BIOS), one per agent
tools/ local installs: Ghidra (with the EE extension), RenderDoc, a JDK if needed
ghidra/ local Ghidra projects (rebuildable caches)
The top folder's name does not matter. What matters is the shape: the checkout sits two levels below the workspace
root, so ../../game from the repository root is the game/ folder.
Why siblings¶
Everything that is not source code lives outside the repository, next to it:
- Safety. The disc image, extracted files and Ghidra databases (which contain the game's code) must never be
committed (LEGAL.md). With them outside the
working tree, a stray
git add -Acannot reach them. - Simplicity. The repository's
.gitignoredoes not have to list every local folder, and a fresh clone has no leftovers to trip over. - Reuse. One Ghidra install, one emulator and one scratch area serve the main checkout and every worktree.
Bootstrap¶
coney-tools workspace init sets the workspace up for you. Two small launchers install
uv when it is missing and run it: bootstrap.sh on Linux and macOS, bootstrap.ps1 on
Windows. From a fresh clone at <workspace>/repos/coney:
When uv is missing, a launcher runs Astral's installer for the uv version pinned at its top
(astral.sh/uv/<version>/install.sh, or install.ps1 on Windows). That installer puts uv in ~/.local/bin
(%USERPROFILE%\.local\bin on Windows) and adds it to your PATH: in your shell profiles (.profile, .bashrc,
.zshrc, ...) on Linux and macOS, in your user PATH on Windows. Set UV_NO_MODIFY_PATH=1 first to keep your
profiles as they are, or install uv yourself.
Or with no clone yet, from the folder that should become the workspace, with the launcher downloaded on its own and
the repository's address from the README: sh bootstrap.sh --repo-url URL. Inside a checkout the command is also
uv run --project python coney-tools workspace init. It:
- creates the folders of the layout that are missing;
- clones the repository into
repos/coneywhen run outside a checkout (--repo-url); - downloads the research tools you ask for with
--fetch(below). The launchers add--fetch ccachewhen you name nothing to fetch, so a new machine gets the compiler cache (Windows; on Linux and macOS init says to install the package); - writes
repos/coney/coney.local.tomlfrom the example when there is none, filling injdk_homewhen it finds a JDK 21, andghidra_mcp_repoandpcsx2_mcp_dirwhen those tools are intools/(pcsx2_mcp_dirnames the fork's downloaded release,tools/PCSX2-MCP-v1.1.0-win64, or, when there is one, the newest build made from the clone,tools/pcsx2-mcp/release/PCSX2-MCP-coney-v2.9.129.N-win64); - checks the prerequisites (Building and testing): Git, the C++ compiler, CMake 3.28+
and Ninja, which the engine needs, and a JDK 21 and Node.js 18+, which only the research tools need, and ccache,
which makes builds faster but is not needed. It prints what is missing and what to install; it installs none of it
(ccache on Windows comes with
--fetch ccache).
Every step that is already done is skipped, so running it again is safe; an existing coney.local.toml is never
changed. --dry-run prints the steps without doing any. --workspace DIR names the workspace instead of finding it
around the checkout (a checkout must sit in a workspace's repos/ folder). A plain clone elsewhere needs no
workspace: the tools then keep the folders the layout puts beside the checkout (scratch/, cache/ccache,
tools/, ghidra/, emulators/) in a folder beside the clone named after it, <clone>-workspace
(~/code/coney uses ~/code/coney-workspace; config.workspace_root). Never inside the clone: Ghidra projects,
emulators and a BIOS stay out of the repository (Why siblings). A path set in coney.local.toml
always wins. The exit code is 1 when the engine's
prerequisites are missing, 2 for a usage error, 0 otherwise.
Never your game or firmware. The bootstrapper never downloads or asks for game files or a BIOS
(LEGAL.md). Put your own disc image in game/, and the BIOS dumped from your own console in each
PCSX2 copy's bios/ folder.
Fetching the research tools¶
Engineers studying the original need Ghidra and PCSX2. --fetch TOOL (repeatable, or --fetch all) downloads
each from its own project's GitHub releases (or clones its repository) at the version below, checks it against the
SHA-256 pinned in
python/src/coney_tools/workspace_fetch.py and unpacks it; downloads are kept in scratch/downloads/. Every file
of a tool is downloaded and checked before any is unpacked, and the tool's folder appears only once it is complete,
so a failed or interrupted fetch leaves nothing half-installed: run the command again to retry it. An archive entry
that would land outside the tool's folder is refused.
--fetch |
What | Into | Licence |
|---|---|---|---|
ghidra |
Ghidra 12.1.4, with the Emotion Engine extension v2.1.38 (built for 12.1.4) in its Ghidra/Extensions/ |
tools/ghidra_12.1.4_PUBLIC |
Apache-2.0 both |
ghidra-mcp |
ghidra-mcp's repository at the commit Coney was set up with (7.0.0 pre-release) | tools/ghidra-mcp |
Apache-2.0 |
ccache |
ccache 4.14.1, the compiler cache coney-tools build uses; Windows only (the release CI's Windows job installs), on Linux and macOS install the ccache package with apt or Homebrew |
tools/ccache-4.14.1 |
GPL-3.0 |
pcsx2-mcp-release |
the PCSX2-MCP fork's release v1.1.0 (Windows only): PCSX2 2.9.129 built with the debug server, the MCP server with its dependencies in pcsx2-mcp-server/, and the patch's source |
tools/PCSX2-MCP-v1.1.0-win64 |
GPL-3.0 |
pcsx2-mcp |
the maintainer's PCSX2-MCP fork (Ryubara/PCSX2-MCP) at a pinned commit: its PCSX2 2.9.129 patch and the Node.js MCP server's source, to change the fork |
tools/pcsx2-mcp |
GPL-3.0 |
A stock PCSX2 is never downloaded: every copy runs the fork's build, the release or one made from the clone (PCSX2 copies).
What is left to do by hand, which the command reminds you of:
- Ghidra: compile the extension's
r5900.slaonce and importSLUS_212.15:coney-tools ghidra setupdoes both and applies our function names (Starting your own project). - ghidra-mcp: build its headless server (
target/GhidraMCPHeadless.jar) with Maven 3.9+ (ghidra-mcp on your machine); the agent bridge runs from the source as it is. - PCSX2-MCP: with the release (
--fetch pcsx2-mcp-release), runconey-tools pcsx2 setup-copies(PCSX2 copies) and put your BIOS in each copy. Only to change the fork, build the clone as its README says (pcsx2-plugin\prepare.ps1, then CMake, thennpm ciandnpm run buildinpcsx2-mcp-server/), into a release folderrelease/PCSX2-MCP-coney-v2.9.129.N-win64that holds PCSX2's program withpcsx2-mcp-server/beside it; pointpcsx2_mcp_dirat it and runconey-tools pcsx2 setup-copies(PCSX2 copies). Each copy needs your BIOS.
To move to newer versions, change the pins (URL and SHA-256, or the commit) in workspace_fetch.py; GitHub shows
each release asset's SHA-256.
PCSX2 copies¶
For the project's agents
Several copies, their ports and the claims (coney-tools pcsx2 claim) let several agents run the original at once.
A person alone needs one copy: set pcsx2_copies = 1 in coney.local.toml, run coney-tools pcsx2 setup-copies
once and start that PCSX2 yourself.
Several agents run the original at once, each in its own portable PCSX2 copy under pcsx2_root
(../../emulators): pcsx2, pcsx2-b, pcsx2-c, ... (pcsx2_copies, 5 by default). Every copy runs the
PCSX2-MCP fork's build named by pcsx2_mcp_dir and listens on its own two ports, copy i (pcsx2 is 0) on:
Setting in inis/PCSX2.ini |
Value | Port of pcsx2 .. pcsx2-e |
|---|---|---|
[EmuCore] EnablePINE, PINESlot |
true, pcsx2_pine_base + i |
28011 .. 28015 |
[EmuCore] EnableDebugServer, DebugServerPort |
true, pcsx2_debug_base + i |
21512 .. 21516 |
[InputSources] SDL |
false: the user's gamepad never drives an agent's game |
coney-tools pcsx2 setup-copies [--count N] [--dry-run] makes the copies that are missing and updates the rest
(pcsx2). It copies only the build's program files over a copy (and removes old program
files the build no longer has, such as a stock PCSX2's updater.exe), writes portable.txt, and sets the keys above
in the copy's ini, leaving every other setting as it was. A copy's own inis/, bios/, memcards/, sstates/,
recordings/ and the other folders PCSX2 writes are never replaced. A new copy starts from the first copy's ini and
bios/. A copy whose PCSX2 runs is refused (exit code 1); close it and run the command again. Run it again after
every new fork build.
Save states made by another PCSX2 version (the copies ran a stock PCSX2 2.9.94 until 2026-10-20) stay in each copy's
sstates/, but PCSX2 2.9.129 may refuse to load them: make the states a task needs again on the new build.
Creating the folders by hand¶
From the folder where you want the workspace (any name works):
mkdir Coney
cd Coney
mkdir repos worktrees scratch game emulators tools ghidra
git clone <the Coney repository URL> repos/coney
Then put your own files in place:
game/: your disc image and the files you extract from it. Coney never ships these and you never commit them.emulators/pcsx2/,pcsx2-b/, ...: the PCSX2 copies, made byconey-tools pcsx2 setup-copies(PCSX2 copies), each with your BIOS.tools/: Ghidra 12.1.4 with the Emotion Engine extension (setup in Ghidra + ghidra-mcp) and a JDK 21 if you do not have one elsewhere.
ghidra/ stays empty until the Ghidra tooling creates a project in it.
coney.local.toml¶
Tools find the sibling folders through coney.local.toml in the repository root. The file is untracked. Copy the
shipped example and adjust it:
Paths are relative to the repository root or absolute. The keys and their defaults:
| Key | Default | What it points at |
|---|---|---|
game_dir |
../../game |
your disc image and files extracted from it |
ghidra_install |
../../tools/ghidra_12.1.4_PUBLIC |
Ghidra 12.1.4 with the Emotion Engine extension |
ghidra_projects |
../../ghidra |
local Ghidra projects |
pcsx2_dir |
../../emulators/pcsx2 |
portable PCSX2 with PINE enabled |
pcsx2_root |
../../emulators |
folder holding the pcsx2, pcsx2-b, ... copies that coney-tools pcsx2 claim hands out |
pcsx2_copies |
5 |
a number: how many copies pcsx2 setup-copies makes |
pcsx2_pine_base |
28011 |
a number: the PINE port of copy pcsx2; copy i uses this + i |
pcsx2_debug_base |
21512 |
a number: the debug server port of copy pcsx2; copy i uses this + i |
pcsx2_claims_dir |
pcsx2-claims in the main checkout's ../../scratch (<clone>-workspace/scratch in a plain clone) |
where those claims live; one place every worktree sees, whatever its scratch_dir |
build_claims_dir |
build-claims in the main checkout's ../../scratch (<clone>-workspace/scratch in a plain clone) |
where coney-tools build keeps its slots and queue; one place every worktree sees, whatever its scratch_dir |
build_slots |
2 |
a number: how many builds and test runs coney-tools build lets run at once |
build_fast_lane_steps |
50 |
a number: a build with fewer steps out of date by themselves (ninja -n -d explain causes, build) uses the fast lane; 0 turns the lane off |
build_fast_lane_tests |
20 |
a number: a -R or -L test run that selects at most this many tests uses the fast lane; 0 turns it off |
build_fast_lane_test_seconds |
300 |
a number: such a run takes a main slot anyway when its tests' recorded times add up to more seconds than this (build); 0 turns that off |
ccache_dir |
cache/ccache in the main checkout's ../.. (<clone>-workspace/cache/ccache in a plain clone) |
the compiler cache every checkout's coney-tools build shares |
ccache_program |
the copy workspace init --fetch ccache put in ../../tools/ccache-4.14.1, else ccache on PATH |
the ccache executable |
deps_dir |
cache/deps in the main checkout's ../.. (<clone>-workspace/cache/deps in a plain clone) |
where coney-tools build keeps the dependency sources, a folder per version of the pins |
ccache_size_gb |
25 |
a number: the compiler cache's size cap in GB (ccache drops the least recently used objects beyond it); 0 turns the cache off |
scratch_dir |
../../scratch (<clone>-workspace/scratch in a plain clone) |
captures and tool output |
jdk_home |
none: you must set it | a JDK 21 home, needed to run Ghidra |
ghidra_mcp_repo |
none: you must set it | a built ghidra-mcp checkout, used read-only |
pcsx2_mcp_dir |
none (init sets it to the clone's newest build under tools/pcsx2-mcp/release/, else to the fetched release tools/PCSX2-MCP-v1.1.0-win64) |
a PCSX2-MCP fork build, the release or one made from the clone: the copies' PCSX2 (pcsx2 setup-copies) and the pcsx2 agent server in its pcsx2-mcp-server/; a server-only folder (dist/index.js) serves the agent server alone |
ffmpeg_dir |
none (PATH) |
the folder holding ffmpeg and ffprobe, for in-game clips (clips); Coney itself reads CONEY_FFMPEG |
Each line is key = "value" (the number keys are written without quotes: build_slots = 2); a # starts a
comment. A tool that needs a folder that does not exist stops with an
error naming the key, the path it resolved and the config file it used, so a wrong entry is easy to find.
Worktrees¶
Extra worktrees go under worktrees/coney/<branch>/ in the workspace and nowhere else, so they stay out of the
main checkout and share its siblings. From the repository root:
A worktree has no coney.local.toml of its own, and needs none: every tool run from a worktree (coney-tools
and tools/start-ghidra-mcp.ps1) finds the main checkout through git's common dir, reads its coney.local.toml and
resolves the relative paths from the main checkout, so ../../game is the same game/ folder from every worktree.
A worktree that does hold its own copy uses that one instead, with paths relative to the worktree. Remove a
worktree when its branch is merged: coney-tools workspace clean does it for you (Cleanup).
Cleanup and free space¶
For the project's agents
This section is about a machine holding many agents' worktrees and builds. A person with one checkout keeps space
free by deleting old build/ folders; coney-tools workspace disk shows the free space.
A build of the engine is about 6 GB per preset (build/dev measured 6.4 GB on 2026-10-20, with /Z7 objects) and each
track has a worktree, so unremoved worktrees fill a drive quickly: once about ninety of them left 3 GB free and broke
builds. Two commands keep that from happening; run them from the main checkout or any worktree.
Deleting a build/ folder is cheap since the shared compiler cache: a fresh dev
folder of the same sources builds in about 2 minutes from the cache (98.7 % hits) instead of about 11, so the rules
below still delete idle folders and agents still delete their own when they finish a unit. The cache itself, in
cache/ccache, is never deleted by clean: it keeps itself under ccache_size_gb (25 GB), which the drive's
30 GB floor allows for.
uv run --project python coney-tools workspace clean [--apply] [--build-hours N] [--worktree-hours N] [--fetch]
[--min-gb N] [--margin-gb N] [--shell-hours N] [--log-idle-hours N]
uv run --project python coney-tools workspace disk [--min-gb N]
workspace clean is a dry run until --apply is given. It lists every worktree under worktrees/coney/ and
what it would do, then the bytes it would reclaim, then the stale processes; with --apply it acts and prints the
free space before and after.
- Removes a worktree (
git worktree remove, then its local branch) when all of these hold: no uncommitted changes (untracked files count), no.coney-busyfile, not locked by git, not the folder you run it from, untouched for--worktree-hours(default 1, so a branch just created frommainis not taken for a merged one), and every commit is merged. A branch is merged whenorigin/maincontains it, or when GitHub's merged pull requests (read once for all worktrees:gh pr list --state merged --limit 1000 --json headRefName,headRefOid) include one from the branch whose head is the branch's tip or a later commit; squash merges leave the branch's commits out ofmain, sogit branch --mergedalone would keep them all. Withoutgh(or when it fails), only whatorigin/maincontains counts, so rungit fetch(or pass--fetch) first; the rest is kept, never guessed. - Deletes
build/(generated, ignored by git) from every worktree that is not busy and has not been touched for--build-hours(defaultworkspace.build_idle_hours, 4; it is only a rebuild): the newest modification time of its tracked and untracked files, of git'sHEADfor it and of the build folder and the entries one and two levels in (a running build writes there). Sources are never touched this way, so it also applies to a worktree with uncommitted or unmerged work. - Disk pressure (
--min-gb, defaultworkspace.disk_min_free_gb, 30;--margin-gb, defaultworkspace.disk_margin_gb, 10). After the two steps above, if the drive still has less than the floor free,cleandeletes thebuild/folders of the remaining worktrees, the one last used longest ago first, until the free space reaches the floor plus the margin (40 GB by default), re-reading the free space after each. This is what keeps the drive above the floor when twenty-five open branches each hold a 6 GB build used within the last hours, which the age rule alone never touches. Onlybuild/folders go in this mode, never a worktree, a source file or a branch, so uncommitted and unmerged work is safe. Skipped: a worktree with the busy file, the folder you run it from, abuild/that git does not ignore, and a worktree that holds a build slot or waits for one (a slot claim or ticket inscratch/build-claims/naming it; without that folder, nothing is skipped on that account). Each deletion is printed with the free space before and after, and a line says so when the drive is still under the floor with no build folder left to delete. A dry run lists the deletions it would make, adding up the sizes. - Never touches the main checkout, a worktree outside
worktrees/, or folders git does not know as worktrees (it only names them; the loose files and logs inworktrees/coney/are for you to delete). A worktree whose folder is gone has its record pruned. - Read-only files (git's packs, some tools' output) are cleared before deleting, and long Windows paths work.
- Stops stale processes (the tree under each, by process id, never by name;
stale_processes.py):- a stale wait: a shell (bash, sh, zsh, pwsh, PowerShell) older than
--shell-hours(default 3) whose command sleeps in anuntilorwhileloop, when every file the loop reads (named in it, or held in a variable it reads:f=/c/x/build.log; while ...; grep "$f") has not changed for--log-idle-hours(default 1) or does not exist. The run it waits for has died, so it would wait for ever and keep its finished agent listed as running. A loop that names no file it can check is never judged; - an orphaned test run: a
ctestorconey_testswhose parent process is gone (or whose parent's pid now belongs to a younger process). Nobody reads its result, and an orphanedctestgoes on starting tests.
- a stale wait: a shell (bash, sh, zsh, pwsh, PowerShell) older than
It never stops the process running clean or any of its ancestors, and lists only the topmost stale process of a
tree. On 2026-10-12 twenty such shells, hours old, and an orphaned ctest were found and killed by hand; the
bounded-waits hook keeps new ones from being started.
The busy file. An agent creates an empty .coney-busy in its worktree root as it starts and deletes it when it is
done (touch .coney-busy; it is in .gitignore). While it exists, clean leaves that worktree and its build/
entirely alone, however old its files look: use it for any stretch where files may not change for hours (a long
build, a test sweep, a wait on the emulator). A file left behind by a dead agent protects its worktree for good;
delete it once you know the work is over. The file's name is workspace.busy_marker of the conventions file.
workspace disk prints the free space of the workspace's drive and exits 1 below --min-gb (default: 30, the
workspace.disk_min_free_gb key of .github/conventions.yml), for hooks and scripts
(coney-tools workspace disk || coney-tools workspace clean --apply); a hook that runs clean --apply every half
hour keeps the drive above the floor on its own. The coordinator runs
clean --apply after every merge round (coney-tools repo audit --local lists what it would remove and the scratch
folders of finished work that still hold files taken from the original), and agents delete their own build/ when they
finish a unit (Briefing). scratch/ is not cleaned automatically: prune built docs sites, logs and
captures there yourself (below).
How long the audit takes. coney-tools repo audit --local is meant to be run as a habit, so it is quick: GitHub is
asked once for the merged pull requests of every branch (not once per worktree), the worktrees are judged and the
scratch folders walked several at a time, and the last activity of a worktree is read only when the audit may report
it. Measured on 2026-10-12 on a workspace of 80 worktrees and 180 scratch folders holding 900,000 files: 11 seconds,
against about 3 minutes before the change (9.7 minutes on 2026-10-11, with more worktrees). The old and the new code
report the same 194 findings.
Scratch¶
scratch/ holds anything you generate while working: emulator captures, memory dumps, tool output, half-finished
experiments. It is never committed and nothing depends on it surviving. Prune it when the work that produced a file
is done, and keep out of it anything you would be sorry to lose; findings belong in
research pages, not in scratch.
Name a scratch folder for its branch (scratch/<branch>/). coney-tools repo audit --local warns (ws.scratch) about
a folder whose branch is merged or gone and that still holds files taken from the original: the types in
workspace.game_capture_suffixes of .github/conventions.yml (PCSX2 save states
and memory cards, traces, memory dumps and WAD entries: .p2s, .ps2, .trace, .dump, .raw, .bin), in any
folder. .bin is generic on purpose, so some build and terminal files match too: the walk never enters a build or
tool tree (workspace.scratch_skip_folders: CMakeFiles, _deps, .gradle, build, venv, .venv,
site-packages, .git, node_modules, in any case), and the finding lists the first five paths (relative to the
folder) and counts the rest: look at them before deleting. .iso is not listed: the scratch ISOs are hard links to
the user's disc and cost no space. They are of no use once the work is
finished, and they are the files that must never leave the machine. Screenshots are not looked at there: upload what
an issue or a pull request needs, then delete them. The folders in workspace.shared_scratch (the emulator's files,
the saved states, the PCSX2 claims, the downloads, the preview builds) belong to the workspace and are never reported.