Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# AGENTS.md - Agent Runbook (BFBB)

This file is the runbook for automated contributions to **BFBB**.

Goal: make steady, source-plausible match progress in this repo and keep that work on one long-lived PR branch instead of spinning up a new PR for every small improvement. When driven by `agentic_loop.py`, keep repeating that loop indefinitely while there are credible code/data improvements to make.

## Project assumptions

- Repo root is the current checkout.
- The active version is `GQPE78`.
- Required local asset: `orig/GQPE78/sys/main.dol`.
- The useful automation artifacts for this project are:
- `build/GQPE78/report.json`
- `build/GQPE78/progress.json`
- `objdiff.json`
- Do not depend on old `.MAP` files, symbol extractors, or state files copied over from another repo.

## Setup

Only run setup steps if the build artifacts are missing or stale.

1. Configure the project:

```sh
python configure.py
```

2. Build the project:

```sh
ninja
```

Success criteria:
- `build/GQPE78/report.json` exists
- `build/GQPE78/main.dol: OK`

## Contribution loop

### 1) Branch and PR management

- If you are already on a non-`main` branch, treat it as the active long-lived automation branch. Stay on it, commit to it, and push back to it.
- If you are on `main` and the worktree is clean, update `main` and create one long-lived branch for the automation run, for example:

```sh
git pull origin main
git checkout -b pr/mega/$(date -u +%s)
```

- Create at most one PR for that branch.
- Once the PR exists, keep pushing more commits to the same branch and updating the same PR.
- Do not create a new PR for each unit or function.
- Do not branch-hop during the loop. If a usable non-`main` branch is already checked out, keep iterating there.
- Do not assume local uncommitted changes are disposable. If unrelated user work is present, leave it alone.

### 2) Pick the next target

Run:

```sh
python tools/agent_select_target.py
```

The selector's default mode is quality-first near-match polishing:
- it reads `build/GQPE78/report.json`
- it ranks incomplete units by balanced progress across fuzzy, code, data, and function match metrics
- it prefers near-complete units with real source paths over source-less or badly imbalanced targets
- it prints a short list of candidate units and a few weak functions

Prefer polishing near-match units first unless there is a concrete reason to chase a lower-match file. Only drop into lower-match exploration when the near-match queue is blocked or exhausted.

### 3) Edit source

Work in `src/` and `include/`.

Priorities:
- recover plausible original source
- improve real code/data matching
- clean up decompiler output into readable C/C++
- define proper structs, enums, types, and linkage
- use real member access instead of pointer math
- prefer fixes that improve source plausibility even when they are not the fastest way to move a score

Avoid:
- hardcoded offsets where a typed field belongs in a struct
- extern hacks added only to move a score
- junk comments, debug notes, or commented-out code
- contrived compiler-coaxing that does not look like plausible original source
- trading readable, typed, source-plausible code for a hacked 100% result

Follow the repo conventions in `CONTRIBUTING.md`.

### 4) Verify

Build after edits:

```sh
ninja
```

Check the result with:
- `build/GQPE78/report.json`
- objdiff, if needed

If the local objdiff CLI exists, a typical command is:

```sh
build/tools/objdiff-cli.exe diff -p . -u <unit> -o - <symbol>
```

Treat real code and data improvements as the signal. Formatting-only churn is not success. A clean, source-plausible near-match is better than an implausible perfect match.

### 5) Commit and update the PR

- Commit meaningful improvements on the current active branch.
- Push the current branch after each meaningful improvement.
- If no PR exists yet and the branch has real progress, create one.
- If a PR already exists for the branch, update that PR instead of opening another one.
- Keep the PR description cumulative as the branch grows.

### 6) Repeat

Stay on the same branch and repeat the loop until the PR has a worthwhile set of high-quality improvements. After each successful push, go back to target selection and continue iterating.

## Quality bar

- Build must pass with `ninja`.
- Progress must be real in report/objdiff output.
- Source should look like plausible original game code.
- Prefer definitions, types, and readable control flow over temporary score hacks.
- A clean 99.99% is better than a hacked or implausible 100%.
- Never revert unrelated user changes.
- Never use destructive git cleanup unless explicitly requested.

## Quick checklist

Before pushing:

1. `ninja` passes.
2. `build/GQPE78/report.json` or objdiff shows real improvement.
3. The code is clean and follows project conventions.
4. The changes stay on the current long-lived PR branch.
5. No new one-off PR was created for this iteration.
138 changes: 138 additions & 0 deletions agentic_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from __future__ import annotations

import argparse
import os
import signal
import shutil
import subprocess
import sys
from pathlib import Path

DEFAULT_TIMEOUT_SECONDS = 25 * 60


def get_repo_root() -> Path:
return Path(__file__).resolve().parent


def get_current_branch(repo_root: Path) -> str:
result = subprocess.run(
["git", "branch", "--show-current"],
cwd=repo_root,
capture_output=True,
text=True,
check=False,
)
branch = result.stdout.strip()
return branch or "DETACHED"


def build_prompt(branch: str) -> str:
if branch not in {"main", "master", "DETACHED"}:
branch_note = (
f"The current branch is '{branch}'. Treat it as the existing long-lived mega-PR branch "
"and keep iterating on it. Commit to it, push to it, and do not create or switch to another branch."
)
else:
branch_note = (
f"The current branch is '{branch}'. If the worktree is clean, create one long-lived PR "
"branch for this automation run and keep reusing it for every later commit."
)

return (
"Follow the instructions in AGENTS.md in this repo. Never ask the user for input. "
f"{branch_note} "
"Use tools/agent_select_target.py to choose the next target from build/GQPE78/report.json. "
"Prefer the selector's default quality-first near-match ranking. "
"Prioritize balanced near-matches that are close across fuzzy, code, data, and function metrics. "
"Focus on plausible BFBB source and real match improvements. "
"Prefer typed, readable, source-plausible code over contrived score hacks; a clean near-match is better than a hacked 100%. "
"Do not create tiny one-off PRs. If a PR already exists for the branch, keep updating that same PR. "
"After each meaningful improvement, build, verify, commit, push the active branch, and continue iterating."
)


def resolve_codex_command() -> list[str]:
if os.name == "nt":
candidates = ("codex.exe", "codex.cmd", "codex.bat", "codex")
else:
candidates = ("codex",)

for candidate in candidates:
resolved = shutil.which(candidate)
if resolved:
return [resolved]

raise FileNotFoundError(
"Could not find the Codex CLI in PATH. Install Codex or add its executable location to PATH."
)


def terminate_process_tree(proc: subprocess.Popen[bytes]) -> None:
if os.name == "nt":
subprocess.run(
["taskkill", "/F", "/T", "/PID", str(proc.pid)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
return

try:
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
except ProcessLookupError:
pass


def run_once(repo_root: Path, timeout_seconds: int) -> int:
prompt = build_prompt(get_current_branch(repo_root))
codex_command = resolve_codex_command()
popen_kwargs = {
"cwd": repo_root,
}

if os.name == "nt":
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
else:
popen_kwargs["start_new_session"] = True

proc = subprocess.Popen(codex_command + ["exec", "--yolo", prompt], **popen_kwargs)

try:
return proc.wait(timeout=timeout_seconds)
except subprocess.TimeoutExpired:
terminate_process_tree(proc)
return 124


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run Codex in a persistent BFBB automation loop.")
parser.add_argument(
"--timeout",
type=int,
default=DEFAULT_TIMEOUT_SECONDS,
help=f"per-iteration timeout in seconds (default: {DEFAULT_TIMEOUT_SECONDS})",
)
parser.add_argument(
"--once",
action="store_true",
help="run a single Codex iteration instead of looping forever",
)
return parser.parse_args()


def main() -> int:
args = parse_args()
repo_root = get_repo_root()

while True:
exit_code = run_once(repo_root, args.timeout)
if args.once:
return exit_code

if exit_code not in {0, 124}:
print(f"codex exited with status {exit_code}", file=sys.stderr)


if __name__ == "__main__":
raise SystemExit(main())
4 changes: 2 additions & 2 deletions src/PowerPC_EABI_Support/src/Runtime/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ asm void __cvt_ull_dbl(void)

asm void __cvt_sll_flt(void)
{
nofralloc;
stwu r1, -0x10(r1);
clrrwi.r5, r3, 31;
beq L_802BA62C;
Expand Down Expand Up @@ -837,7 +838,6 @@ L_802BA6B4:;
lfd f1, 0x8(r1);
frsp f1, f1;
addi r1, r1, 0x10;
frfree; // Build error said to add this
blr
}

Expand Down Expand Up @@ -968,4 +968,4 @@ end:;

#ifdef __cplusplus
}
#endif
#endif
Loading
Loading