Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3522993
docs: plan inventory item system design
heyradcode Aug 7, 2026
ffa66d4
feat(inventory): add ItemCore, the ERC-1155 item contract
heyradcode Aug 7, 2026
8cf5a40
feat(inventory): deploy ItemCore from the Ignition module
heyradcode Aug 7, 2026
9c7afe9
feat(inventory): index item balances and equipment in the subgraph
heyradcode Aug 7, 2026
ac88a94
feat(inventory): index item balances and equipment in indexer-go
heyradcode Aug 7, 2026
26215ed
feat(inventory): persist item balances and equipment from indexer-go
heyradcode Aug 7, 2026
b33608c
feat(inventory): add the item catalog, projections and entitlement ta…
heyradcode Aug 7, 2026
ee1d2c0
feat(inventory): add the v1 item catalog and its seeder
heyradcode Aug 7, 2026
fac9c0f
feat(inventory): add the inventory read service
heyradcode Aug 7, 2026
8d094b8
feat(inventory): expose the inventory reads over GraphQL
heyradcode Aug 7, 2026
5499ceb
feat(inventory): add the item write surface
heyradcode Aug 7, 2026
05c8388
feat(inventory): pay item drops from settled battles
heyradcode Aug 7, 2026
892fb59
feat(inventory): add the shared inventory hooks
heyradcode Aug 7, 2026
99532f7
feat(inventory): add the inventory adapter for equip and unequip
heyradcode Aug 7, 2026
d094c2a
feat(inventory): add the inventory page
heyradcode Aug 7, 2026
e81a42b
feat(inventory): add the equip panel
heyradcode Aug 8, 2026
1080447
feat(protocol): add equipment to the battle snapshot
heyradcode Aug 8, 2026
dc41dd5
feat(protocol): put the item catalog in the ruleset
heyradcode Aug 8, 2026
c811918
feat(protocol): make equipment change the fight
heyradcode Aug 8, 2026
b89f6bc
feat(indexer-go): port equipment modifiers to the Go combat verifier
heyradcode Aug 8, 2026
dd0ca76
feat(battle): fight with the equipment frozen in the snapshot
heyradcode Aug 8, 2026
65ac46c
feat(verifier): replay and audit geared receipts
heyradcode Aug 8, 2026
75513c1
docs: record what the inventory feature actually shipped
heyradcode Aug 8, 2026
9af676c
ci: name the equipment vector suites in the parity workflow
heyradcode Aug 8, 2026
b81e610
refactor(protocol): one summation for equipment, shared by every repl…
heyradcode Aug 8, 2026
0f0cf69
refactor(inventory): drop code nothing calls
heyradcode Aug 8, 2026
edf11e2
test(inventory): cover the two new screens
heyradcode Aug 8, 2026
319e8a2
test(inventory): cover the HTTP layer and pending items
heyradcode Aug 8, 2026
863930a
fix(inventory): make the catalog seeder actually able to write
heyradcode Aug 8, 2026
ffe1746
fix(inventory): stop escrowed equipment being burned
heyradcode Aug 8, 2026
779d398
docs(inventory): record the Base Sepolia ItemCore deployment
heyradcode Aug 8, 2026
8e583aa
fix(subgraph): index ItemCore from its own deploy block
heyradcode Aug 8, 2026
a530de9
feat(inventory): warn before sending a pet that is wearing gear
heyradcode Aug 8, 2026
536b883
feat(inventory): stop a geared pet being transferred
heyradcode Aug 8, 2026
35b4410
fix(contracts): resolve the deployment id in the upgrade and verify s…
heyradcode Aug 8, 2026
1078455
feat(items): serve static art and ERC-1155 metadata for the catalog
heyradcode Aug 9, 2026
62c3676
feat(items): generate item art with Workers AI, cached once per type
heyradcode Aug 9, 2026
e9fe59d
feat(inventory): show item art on the bag
heyradcode Aug 9, 2026
d9c2792
fix(inventory): let the bag scroll
heyradcode Aug 9, 2026
a67560b
feat(inventory): sharpen the item card
heyradcode Aug 9, 2026
763a4a5
feat(inventory): move the help button onto the item tile
heyradcode Aug 9, 2026
cc44b94
ui: make the help trigger a round typographic "?"
heyradcode Aug 9, 2026
9e331a4
ui: use bottle and muscle glyphs for the card actions
heyradcode Aug 9, 2026
347d724
feat(inventory): reduce the bag to tiles, move detail into a modal
heyradcode Aug 9, 2026
9f8e8c8
feat(inventory): put rarity, help, count and action in the slot corners
heyradcode Aug 9, 2026
111307b
ui: use the muscle glyph for every item action
heyradcode Aug 9, 2026
9963763
ui: use the shared refresh icon button
heyradcode Aug 9, 2026
12bbd44
cleanup: remove the Shard Forge entry
heyradcode Aug 9, 2026
9ad5841
feat(inventory): use PetSelect for the pet picker
heyradcode Aug 9, 2026
84503a3
feat(pets): show equipped gear on pet art in the gallery and arena
heyradcode Aug 9, 2026
9e77113
ui: stack the equipped-gear badges vertically
heyradcode Aug 9, 2026
d9b0318
ui: tint equipped-gear badges by the pet's rarity
heyradcode Aug 9, 2026
6dd3043
fix(inventory): refresh gallery gear badges after an equip
heyradcode Aug 9, 2026
12f954a
refactor(nav): fold defence into the battle page and equipment into t…
heyradcode Aug 9, 2026
72db124
feat(inventory): show equipped gear in the bag and open the panel on …
heyradcode Aug 9, 2026
abf6766
refactor: apply review findings across the item feature
heyradcode Aug 9, 2026
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
22 changes: 16 additions & 6 deletions .github/workflows/image-generator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ name: Image generator
# services/image-generator/README.md), so the root `pnpm lint` / `pnpm test` aggregates
# and the Coverage workflow do not reach it. Without this workflow nothing runs its
# suite at all.
# The path filter must also list the files `src/solanaLayout.test.ts` reads, not just
# this package. That suite pins the PetAccount byte layout against the Anchor IDL and
# `pet.rs`, so a Solana account change breaks it while touching nothing under
# `services/image-generator/`. Filtered on this package alone the suite simply does not run, which
# is how `open_to_challenges` was removed from the program while the decoder kept its
# byte — every field after it misaligned, and a pet rendered as a different pet.
# The path filter must also list the files this package's cross-boundary suites READ, not
# just the package itself. Two of them reach outside it:
#
# src/solanaLayout.test.ts pins the PetAccount byte layout against the Anchor IDL and
# `pet.rs`, so a Solana account change breaks it while touching nothing under
# `services/image-generator/`. Filtered on this package alone the suite simply does not
# run, which is how `open_to_challenges` was removed from the program while the decoder
# kept its byte — every field after it misaligned, and a pet rendered as a different pet.
#
# src/items.test.ts asserts `src/items.ts` still matches the backend item catalog, which
# this package cannot import at build time (it installs `--ignore-workspace`). The drift
# it guards against is caused by editing the *backend* catalog, so without that path
# listed here the guard never runs on the change that trips it — a new item merges with a
# stale copy, green.
#
# The two lists below are duplicated on purpose: GitHub Actions does not support YAML
# anchors, so factoring them out would silently disable the filter rather than share it.
Expand All @@ -21,13 +29,15 @@ on:
- '.github/workflows/image-generator.yml'
- 'contracts/solana/cryptopets/programs/cryptopets/src/state/**'
- 'services/indexer-go/internal/solana/idl/**'
- 'backend/src/features/inventory/catalog.data.ts'
push:
branches: [main]
paths:
- 'services/image-generator/**'
- '.github/workflows/image-generator.yml'
- 'contracts/solana/cryptopets/programs/cryptopets/src/state/**'
- 'services/indexer-go/internal/solana/idl/**'
- 'backend/src/features/inventory/catalog.data.ts'

permissions:
contents: read
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ name: Combat parity
# `verifier`, and the three suites that actually replay the vectors were covered by
# neither:
#
# protocol tests/combat/goldenVectors.test.ts (the canonical TS engine)
# services/indexer-go internal/combat/combat_golden_test.go (the independent Go port)
# contracts/ethereum test/XpFormula.test.ts (the XP fixture)
# protocol tests/combat/goldenVectors.test.ts (the canonical TS engine)
# protocol tests/combat/equipmentVectors.test.ts (gear, roadmap §4)
# services/indexer-go internal/combat/combat_golden_test.go (the independent Go port)
# services/indexer-go internal/combat/equipment_golden_test.go (gear, the same file)
# contracts/ethereum test/XpFormula.test.ts (the XP fixture)
#
# `equipment.json` is read by the two live ports only — the frozen Solana port predates
# equipment and never applies it, so it is not a witness to that file. Its first case
# reproduces a `battle.json` row, which is how an ungeared fight is proven unchanged.
#
# §F's circuit breaker only has value while the TS and Go ports are independent and both
# match the vectors. A drift that CI never runs is a circuit breaker nobody armed.
Expand Down
7 changes: 4 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ Normative language: `MUST`/`MUST NOT` are mandatory. `SHOULD`/`SHOULD NOT` are e
## Non-Negotiables

- `MUST NOT` change Solana's frozen combat port (`game/battle_sim.rs`, `game/xp.rs`). It has no caller left in the program, but its golden-vector tests are what still prove `contracts/test-vectors/{battle,xp}.json` describe what actually settled on that chain. A bug found there is fixed forward in the live ports below, under a new `rulesetVersion`, never by patching the frozen one. **The Solidity port is gone**: `CombatSim.sol` was deleted once it had no on-chain caller, which also removed `battle.json`'s Solidity generator and validator. `battle.json` itself is unchanged and still gates the live ports.
- `MUST` keep the two **live** combat ports in step with each other and with the golden vectors: `protocol/src/combat/` (the canonical engine, re-exported from `shared/src/utils/combat` for existing importers) and `services/indexer-go/internal/combat/` (the independent verifier). Changing one without the other re-breaks the circuit breaker in §F, whose whole value is that the two were written to disagree if either drifts. This covers XP and level progression too (`protocol/src/combat/xp.ts`, validated against `contracts/test-vectors/xp.json`), so an XP or decay change is a both-ports change. `services/indexer-go/internal/combat/xp.go` still covers the formula and the decay but not level-up.
- `MUST NOT` edit `contracts/test-vectors/{battle,xp}.json` to make a failing test pass — this holds more strongly now, not less. The vectors are the only mechanical link left between the frozen ports and the live ones. A live port that fails them has drifted away from the rules real battles were settled under.
- `MUST` keep the two **live** combat ports in step with each other and with the golden vectors: `protocol/src/combat/` (the canonical engine, re-exported from `shared/src/utils/combat` for existing importers) and `services/indexer-go/internal/combat/` (the independent verifier). Changing one without the other re-breaks the circuit breaker in §F, whose whole value is that the two were written to disagree if either drifts. This covers XP and level progression too (`protocol/src/combat/xp.ts`, validated against `contracts/test-vectors/xp.json`), so an XP or decay change is a both-ports change. `services/indexer-go/internal/combat/xp.go` still covers the formula and the decay but not level-up. It also covers **equipment modifiers** (roadmap §4): `protocol/src/combat/equipment.ts` and `services/indexer-go/internal/combat/equipment.go`, both validated against `contracts/test-vectors/equipment.json`. The modifiers apply at one specific point — after `extract`, before the skill multipliers — and moving that point in one port without the other changes every geared fight, so the ordering is pinned by a vector case in both.
- `MUST NOT` edit `contracts/test-vectors/{battle,xp,equipment}.json` to make a failing test pass — this holds more strongly now, not less. `battle.json` and `xp.json` are the only mechanical link left between the frozen ports and the live ones, and a live port that fails them has drifted away from the rules real battles were settled under. `equipment.json` is newer and has no frozen witness, but the same rule applies for the same reason: it is what holds the two live ports to one another. Its first case deliberately reproduces a `battle.json` row, so an ungeared fight is proven unchanged rather than assumed.
- `MUST` treat a `snapshot` or `ruleset` schema-version bump as append-only. An absent version means **1**, never "whatever this build implements": every snapshot and published bundle written before those fields existed is version 1 and has receipts signed over it, so defaulting to the current version silently re-encodes them under a layout they were never hashed under and invalidates every signature. Old versions stay listed in `SUPPORTED_VERSIONS` permanently (`protocol/src/domain/schemaVersions.ts`).
- `MUST NOT` assume the `ChainAdapter` interface (`shared/src/hooks/adapters/`) covers more than pet-action mutations and reads. It is a real, shared interface (`useEvmAdapter`/`useSolanaAdapter` both implement it) and every public pet-action hook consumes it for the mutation, but the low-level chain wiring in `frontend/src/chains/{ethereum,solana}/`, the async breed/mint randomness flows, and the combat simulator remain intentionally separate per chain. `useCreatePet` and `useBreedPets` are only chain-blind on the action: both carry the EVM settle lifecycle inline behind `isEvm` guards. See CLAUDE.md's cross-chain interfaces section for the exact boundary.
- `MUST` match the license of the package being edited when adding new files: `contracts/ethereum`, `contracts/solana`, `services/indexer-go`, `proto`, `protocol`, and `verifier` are MIT; everything else, `services/image-generator` included, is PolyForm Noncommercial 1.0.0 (root `LICENSE`). See the table in `README.md`. `protocol` is MIT on purpose (third parties have to be able to replay signed battle receipts), so it `MUST NOT` import from a PolyForm package; a test in that package enforces it. `verifier` is MIT for the same reason and depends on nothing but `protocol`.
- `MUST NOT` assume the root `pnpm lint` / `pnpm test` cover `image-generator`, and `MUST NOT` verify it with `pnpm --filter image-generator <script>`. It is not a pnpm workspace member, so that command prints `No projects matched the filters` **and exits 0**: it reports success having run nothing. Run its scripts from `services/image-generator/` instead. It keeps its own lockfile, installs with `pnpm install --ignore-workspace`, and is checked by its own CI workflow.
Expand Down Expand Up @@ -47,6 +48,6 @@ Full per-package lint/test/build matrix and single-test syntax: see [CLAUDE.md](
Mechanical checks over prose, where they exist:

- ESLint per package (`frontend`, `shared`, `website`, `mobile`), plus a custom CSS-naming check in `frontend` (`lint:css`).
- Golden test vectors (`contracts/test-vectors/{battle,xp}.json`), run by Anchor, `indexer-go`'s `combat_golden_test.go`, and `@cryptopets/protocol`'s `tests/combat/goldenVectors.test.ts` (Vitest), are the cross-language enforcement for combat-simulator parity. Anchor's frozen suite proves the vectors still describe what really settled on Solana; the two live ports prove they have not drifted from it. Hardhat no longer checks `battle.json` — that leg went with `CombatSim.sol`.
- Golden test vectors (`contracts/test-vectors/{battle,xp,equipment}.json`), run by Anchor, `indexer-go`'s `combat_golden_test.go` and `equipment_golden_test.go`, and `@cryptopets/protocol`'s `tests/combat/goldenVectors.test.ts` and `equipmentVectors.test.ts` (Vitest), are the cross-language enforcement for combat-simulator parity. `equipment.json` is read by the two live ports only, since the frozen Solana port predates equipment and never applies it. Anchor's frozen suite proves the vectors still describe what really settled on Solana; the two live ports prove they have not drifted from it. Hardhat no longer checks `battle.json` — that leg went with `CombatSim.sol`.
- CI coverage workflow (`.github/workflows/coverage.yml`) runs frontend/backend/shared vitest coverage on every PR and posts a combined comment. The verifier workflow (`.github/workflows/verifier.yml`) replays a committed receipt corpus through the standalone verifier, and asserts a tampered corpus is rejected.
- There is no repo-wide `agents:check` or module-boundary lint yet. Rely on the per-package commands above and the golden vectors until one exists.
Loading
Loading