Repository navigation
feat(release): publish the farming analyzer as a release asset - #32
Conversation
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>
Dependency Review✅ No vulnerabilities or license issues found.Scanned FilesNone |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 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".
| if ! git merge-base --is-ancestor "${source_commit}" refs/heads/main && | ||
| ! git merge-base --is-ancestor "${source_commit}" refs/remotes/origin/main; then |
There was a problem hiding this comment.
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 👍 / 👎.
| git archive --format=tar --prefix="${package}/" "${source_commit}" LICENSE farming_analyzer | | ||
| gzip -9n >"${output_directory}/${package}.tar.gz" |
There was a problem hiding this comment.
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 👍 / 👎.
| ["@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"}], |
There was a problem hiding this comment.
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 👍 / 👎.
## 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>
Summary
The autonomous playtester (https://github.com/atrinik/playtester) will run
farming_analyzerin the background to choose where Sera farms. Itsdependencies.lock.jsonpins 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 onlyLICENSEandfarming_analyzer/under oneatrinik-tools-farming-analyzer-<version>/directory. The GPLmap-checker-qt/subtree and everything else are excluded.SHA256SUMSfor that archive.scripts/release/package-farming-analyzer.sh TAG OUTPUTrequires avX.Y.Ztag reachable frommain. It builds the archive withgit archive(every member carries the commit time) piped throughgzip -9n(no name or timestamp), so the same tag always yields the same bytes. Semantic-release runs it as an@semantic-release/execpublish step (pinned7.1.0, as in content), and@semantic-release/githubuploadsbuild/release/*. The script follows content'stools/package-release.sh.Validation
tests/test_farming_analyzer_release.py(2 tests) builds the asset twice from a temporary tagged repository. It checks the bytes are identical,SHA256SUMSmatches, and the members are exactlyLICENSEandfarming_analyzer/, withmap-checker-qt/excluded. It also checks that malformed tags and tags not reachable frommainare refused.v3.7.0tag gives a 52,868-byte archive (17 entries) with SHA-256cf4dcce7a78cda1eef3b094c68b13f3a2a4cf6900efd9477c1e31734b6a75ba5.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, andgit diff --check: clean.🤖 Generated with Claude Code