Skip to content

feat(release): publish the farming analyzer as a release asset - #32

Merged
zoeyrose merged 1 commit into
mainfrom
feat/farming-analyzer-release-asset
Oct 17, 2026
Merged

zoeyrose merged 1 commit into
mainfrom
feat/farming-analyzer-release-asset

Conversation

@zoeyrose

Copy link
Copy Markdown
Member

Summary

The autonomous playtester (https://github.com/atrinik/playtester) will run farming_analyzer in the background to choose where Sera farms. Its dependencies.lock.json pins every external input as a release artifact with a SHA-256 digest. Tools releases currently carry no assets, and GitHub's generated source archives aren't guaranteed byte-stable, so there's nothing reliable to pin.

Each release now attaches:

  • atrinik-tools-farming-analyzer-<version>.tar.gz, holding only LICENSE and farming_analyzer/ under one atrinik-tools-farming-analyzer-<version>/ directory. The GPL map-checker-qt/ subtree and everything else are excluded.
  • SHA256SUMS for that archive.

scripts/release/package-farming-analyzer.sh TAG OUTPUT requires a vX.Y.Z tag reachable from main. It builds the archive with git archive (every member carries the commit time) piped through gzip -9n (no name or timestamp), so the same tag always yields the same bytes. Semantic-release runs it as an @semantic-release/exec publish step (pinned 7.1.0, as in content), and @semantic-release/github uploads build/release/*. The script follows content's tools/package-release.sh.

Validation

  • New tests/test_farming_analyzer_release.py (2 tests) builds the asset twice from a temporary tagged repository. It checks the bytes are identical, SHA256SUMS matches, and the members are exactly LICENSE and farming_analyzer/, with map-checker-qt/ excluded. It also checks that malformed tags and tags not reachable from main are refused.
  • Packaging the real v3.7.0 tag gives a 52,868-byte archive (17 entries) with SHA-256 cf4dcce7a78cda1eef3b094c68b13f3a2a4cf6900efd9477c1e31734b6a75ba5.
  • python3 -m unittest discover -s tests, node scripts/release/test-release-policy.cjs, ShellCheck (now also run in CI for the new script), actionlint on both workflows, and git diff --check: clean.

🤖 Generated with Claude Code

Attach a deterministic atrinik-tools-farming-analyzer-<version>.tar.gz
and SHA256SUMS to each release, so consumers such as the autonomous
playtester can pin the analyzer by digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T03:21:08.999726Z c005952 PR opened
🔒 Security Review ✅ Completed 2026-10-17T03:19:41.862769Z c005952 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@zoeyrose
zoeyrose merged commit c624e50 into main Oct 17, 2026
6 checks passed
@zoeyrose
zoeyrose deleted the feat/farming-analyzer-release-asset branch October 17, 2026 03:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c005952726

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +20 to +21
if ! git merge-base --is-ancestor "${source_commit}" refs/heads/main &&
! git merge-base --is-ancestor "${source_commit}" refs/remotes/origin/main; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Permit packaging releases from maintenance branches

When the release workflow runs on a supported N.N.x branch, its new tag normally points to a maintenance-only commit that is not an ancestor of either local or remote main, so both checks fail and the publishCmd aborts before @semantic-release/github can upload the assets. Validate reachability against the active release branch instead of hard-coding main, otherwise maintenance squash merges cannot be released.

AGENTS.md reference: AGENTS.md:L32-L33

Useful? React with 👍 / 👎.

Comment on lines +30 to +31
git archive --format=tar --prefix="${package}/" "${source_commit}" LICENSE farming_analyzer |
gzip -9n >"${output_directory}/${package}.tar.gz"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fix the archive umask before hashing

When a release runs with a non-default repository, global, or system tar.umask, git archive applies that setting to member permission bits, producing different bytes and a different digest for the same tag; for example, tar.umask=0002 and 0022 yield different archives here. This contradicts the documented reproducibility guarantee, so invoke git archive with an explicit tar.umask before hashing it; git-config(1) documents that this variable restricts the permission bits of tar archive entries.

Useful? React with 👍 / 👎.

Comment thread .releaserc.json
["@semantic-release/commit-analyzer", {"preset": "conventionalcommits", "releaseRules": "./scripts/release/release-rules.cjs"}],
["@semantic-release/release-notes-generator", {"preset": "conventionalcommits"}],
["@semantic-release/github", {"successComment": false, "failComment": false}]
["@semantic-release/exec", {"publishCmd": "scripts/release/package-farming-analyzer.sh v${nextRelease.version} build/release"}],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build the asset before creating the release tag

If this packaging command fails on a mainline release—for example because git archive, gzip, or sha256sum errors—semantic-release 25.0.9 has already created and pushed the tag before invoking publish plugins, as shown by the pinned implementation's tag and publish ordering. On rerun that tag is selected as the last release and there are no remaining commits to publish, per the release-selection path, leaving the version without a GitHub release or assets unless a maintainer manually removes the tag. Generate the archive with prepareCmd from the pending release commit so packaging failures occur before tag creation.

AGENTS.md reference: AGENTS.md:L32-L33

Useful? React with 👍 / 👎.

zoeyrose added a commit to atrinik/playtester that referenced this pull request Oct 17, 2026
## Summary

> **Merge together with the stacked
#47 On its own, this PR's
objective toggle only reorders the dashboard ranking; #47 makes autoplay
farm the selected objective's #1 spot.

First step of making the farming analyzer the source of truth for farm
spots. This PR pins the analyzer, runs it in the background from Sera's
live stats, and adds a dashboard **Farming** tab with the objective
toggle. Autoplay doesn't follow it yet: the follow-up PR switches
autoplay to always farm the selected objective's #1 and deletes the
hard-coded tiers and `farm_spots.py`.

### Locked inputs

`dependencies.lock.json` gains two archive inputs. They use the same
fail-closed path as content: digest-pinned HTTPS download, member, size
and path limits, required paths, a tree digest in the bundle marker, and
a read-only cache generation.

- `farming_analyzer`: `atrinik/tools` `v3.8.0` (`c624e500`),
`atrinik-tools-farming-analyzer-3.8.0.tar.gz`, `sha256:3adcce51…`, MIT.
This is the deterministic asset added in
atrinik/tools#32; the published bytes match a
local rebuild of the tag. It unpacks to `analyzer/`.
- `classic_server`: `atrinik/classic` `v5.99.0` (`adcab310`, the release
MAIN runs), `atrinik-classic-server-5.99.0.tar.gz`, `sha256:ea3fd518…`,
GPL-2.0-or-later. The analyzer reads `src/server/exp.c` and
`skill_util.c` from it as text. Nothing from it is compiled, linked,
executed or distributed. It unpacks to `classic-server/`.

`compatibility.py` validates both inputs through one shared
`_validate_archive_spec`, which content now uses too. `_release_specs`
includes them, so `preflight` verifies their tags, commits and asset
digests on GitHub. `install_bundle` and `inspect_bundle` extract and
verify them, and `doctor` gains a `farming-analyzer` check. For
air-gapped installs, `dependencies` also accepts `--analyzer-archive`
and `--server-archive` alongside `--source-archive` and
`--runtime-archive`. `THIRD_PARTY_NOTICES.md`, `PROVENANCE.md`, the
README and `AGENTS.md` record the inputs and the out-of-process
boundary.

### Background analyzer (`farm_analyzer.py`)

- **Inputs:** `combat_profile()` reads the killing weapon school (from
the equipped weapon's required skill) and its level, plus the player
doll: DAM, WC, weapon speed, character level, max HP, speed, AC and
protections. These map to the analyzer's server-formula melee options
(`--melee-damage`, `--weapon-class`, `--weapon-speed`, `--melee-attack
slash=100`, `--player-ac`, `--player-protection TYPE=PERCENT`, …). Only
melee weapon schools are simulated for now; other loadouts report
`unsupported`.
- **Process:** `FarmAnalyzer` owns at most one analyzer process: `nice
-n 10 python -E -s` with the cached analyzer on `sys.path`,
`--content-root` and `--server-root` from the bundle, and `--ranking all
--top 10 --json`. One run ranks every objective, so switching objective
never needs a new run.
- **When it runs:** after a material profile change (school or level, at
least 10% DAM or max HP, at least 5% weapon or movement speed, at least
2 WC or AC, at least 5 points of any protection) or after six hours, at
most every ten minutes. A failure keeps the previous ranking and retries
after fifteen minutes, independent of that ranking's age; a 15-minute
timeout kills a stuck run.
- **Wiring:** in `web` and `autoplay` modes, the CLI builds the analyzer
from the installed bundle and polls it every 30 s while the character is
in game.

### Dashboard

- The new **Farming** tab shows the run status (running, updated N ago
with its duration, or the failure) and the simulated profile. Below that
is a segmented toggle for **XP per hour, Money per hour, Loot per hour,
Balanced, Safety**, then the top five spots for the selected objective:
name, region, location, estimated XP/money/loot per hour, danger, and
monsters.
- The choice is saved per character (`farm_objective` in the character
state record) through the new `farm_objective` action, which rejects
unknown objectives.

### Live analyzer check

With Sera's real stats (slash weapons 22, DAM 44, WC 31, 2.25 s, HP 188,
AC 37, slash 49 / pierce 48 / impact 43 / cleave 54 / acid 12), a run
through the service took 11.0 s. The event loop kept ticking throughout.
The #1 picks:

| Objective | #1 |
| --- | --- |
| XP, loot, balanced, safety | Strakewood Island circuit (est. 4.4M
XP/h) |
| Money | Eld Woods Island circuit |

Two things to act on before autoplay follows these picks:

- **Strakewood:** the analyzer scores Strakewood as the safest spot, but
live play there previously killed Sera (crocodile and frog packs joining
across map seams). The follow-up keeps one exception: a spot where she
dies or has to make an emergency retreat is excluded from the analyzer's
next runs for a while, so she moves to the next #1. The analyzer's
danger model should also learn about those packs.
- **XP estimates:** the estimates are about 30 times her live rate (she
earns 80–150k XP/h), so they're for ranking, not for display as truth.

## Validation

- Full suite on a fresh CMake build: 456 tests OK. New coverage:
- profile extraction, including `weapon_magic` becoming `weaponmagic`
and unsupported loadouts;
  - the material-change thresholds;
- a background run against a fake analyzer: non-blocking, one process at
a time, every objective ranked, no re-run for an unchanged profile,
re-run after a level-up, and the last ranking kept for an unsupported
loadout;
  - a failed run backing off;
  - the isolated, low-priority command line;
- the dashboard `farming_analysis` slice, the `farm_objective` action
and persistence, and the new tab.
- `test_compatibility`: 19 OK, with fixtures and checks extended to both
new inputs (validation failures, extraction, marker identities, doctor).
- Real lock, fresh cache: `dependencies` downloaded and verified both
new artifacts; `doctor` passed all five checks, including
`farming-analyzer`; `preflight` against GitHub passed all seven.
- `node --check web_static/app.js`, `compileall` and `git diff --check`:
clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant