feat: adventurer oracle — general objective checker for Death Mountain adventurers - #31
Conversation
…adventurers
New package `packages/adventurer_oracle` — the reusable primitive for adventurer
achievements (and, later, a thin Budokan entry-requirement validator).
- create_objective(config) -> u32 (owner-gated) + completed_objective(token_id,
objective_id) -> bool, conforming to game-components IMinigameObjectives /
IMinigameObjectivesDetails (same SRC5 id) — generalizes the score-only impl to
any adventurer metric.
- Metric: xp/level/gold/health/beast_health/stat_upgrades + all 7 stats + item
checks (held-anywhere/equipped/in-bag/greatness). Comparator: AtLeast/AtMost/
Equal/GreaterThan/LessThan/NotEqual. ObjectiveConfig { settings_id, metric,
comparator, target, aux }.
- Reads live state from three prod sources (constructor args): GameCore
load_assets (stat-boosted adventurer + bag), Denshokan settings_id(token_id),
GameToken settings_exist — validates the token's settings_id against the
objective before checking.
- oracle_lib holds pure evaluation (level/greatness formulas, comparators),
unit- + fuzz-tested; mock_game drives deterministic integration tests.
scarb build clean; snforge 19 passed (incl. 2 fuzz); scarb fmt --check clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GPT Code ReviewReview execution failed (exit code 1). The Codex CLI log was not posted because it can contain the full review prompt. |
Claude Code ReviewReview execution failed. Spending cap reached resets 7pm |
There was a problem hiding this comment.
Code Review
This pull request introduces the adventurer_oracle package, which implements a reusable objective-checker for Death Mountain adventurer NFTs. It includes the AdventurerOracle contract, pure evaluation logic in oracle_lib, local state struct mirrors, mock contracts, and comprehensive tests. The review feedback highlights two critical bugs in oracle_lib.cairo where checking for an item ID of 0 (representing an empty slot) can incorrectly return true for both is_equipped and is_in_bag. Additionally, it is recommended to add input validation for objective configurations during creation in oracle.cairo to prevent registering invalid objectives.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| pub fn is_equipped(equipment: Equipment, item_id: u8) -> bool { | ||
| equipment.weapon.id == item_id | ||
| || equipment.chest.id == item_id | ||
| || equipment.head.id == item_id | ||
| || equipment.waist.id == item_id | ||
| || equipment.foot.id == item_id | ||
| || equipment.hand.id == item_id | ||
| || equipment.neck.id == item_id | ||
| || equipment.ring.id == item_id | ||
| } |
There was a problem hiding this comment.
If item_id is 0 (which represents an empty slot), is_equipped will return true if any of the equipment slots is empty. Since 0 is not a valid item ID, checking if an adventurer has item 0 equipped should return false. We should add a check to ensure item_id != 0 before checking the slots.
pub fn is_equipped(equipment: Equipment, item_id: u8) -> bool {
item_id != 0
&& (equipment.weapon.id == item_id
|| equipment.chest.id == item_id
|| equipment.head.id == item_id
|| equipment.waist.id == item_id
|| equipment.foot.id == item_id
|| equipment.hand.id == item_id
|| equipment.neck.id == item_id
|| equipment.ring.id == item_id)
}
| pub fn is_in_bag(bag: Bag, item_id: u8) -> bool { | ||
| bag.item_1.id == item_id | ||
| || bag.item_2.id == item_id | ||
| || bag.item_3.id == item_id | ||
| || bag.item_4.id == item_id | ||
| || bag.item_5.id == item_id | ||
| || bag.item_6.id == item_id | ||
| || bag.item_7.id == item_id | ||
| || bag.item_8.id == item_id | ||
| || bag.item_9.id == item_id | ||
| || bag.item_10.id == item_id | ||
| || bag.item_11.id == item_id | ||
| || bag.item_12.id == item_id | ||
| || bag.item_13.id == item_id | ||
| || bag.item_14.id == item_id | ||
| || bag.item_15.id == item_id | ||
| } |
There was a problem hiding this comment.
Similar to is_equipped, if item_id is 0, is_in_bag will return true if any of the 15 bag slots is empty. We should add a check to ensure item_id != 0 before checking the slots.
pub fn is_in_bag(bag: Bag, item_id: u8) -> bool {
item_id != 0
&& (bag.item_1.id == item_id
|| bag.item_2.id == item_id
|| bag.item_3.id == item_id
|| bag.item_4.id == item_id
|| bag.item_5.id == item_id
|| bag.item_6.id == item_id
|| bag.item_7.id == item_id
|| bag.item_8.id == item_id
|| bag.item_9.id == item_id
|| bag.item_10.id == item_id
|| bag.item_11.id == item_id
|| bag.item_12.id == item_id
|| bag.item_13.id == item_id
|| bag.item_14.id == item_id
|| bag.item_15.id == item_id)
}
| self.assert_only_owner(); | ||
|
|
||
| // Validate the referenced game settings exist. | ||
| let exists = IGameSettingsSourceDispatcher { | ||
| contract_address: self.settings_source.read(), | ||
| } | ||
| .settings_exist(config.settings_id); | ||
| assert(exists, Errors::SETTINGS_MISSING); |
There was a problem hiding this comment.
It is highly recommended to validate the config parameters at objective creation time to prevent registering invalid objectives that can never be completed (e.g., item ID 0, item ID > 255, greatness threshold > 20, or non-zero aux for scalar metrics).
self.assert_only_owner();
// Validate config parameters
match config.metric {
Metric::ItemHeldAnywhere | Metric::ItemEquipped | Metric::ItemInBag => {
assert(config.target > 0 && config.target <= 255, 'Oracle: invalid item id');
assert(config.aux == 0, 'Oracle: aux must be 0');
},
Metric::ItemGreatness => {
assert(config.target > 0 && config.target <= 255, 'Oracle: invalid item id');
assert(config.aux <= 20, 'Oracle: greatness max is 20');
},
_ => {
assert(config.aux == 0, 'Oracle: aux must be 0');
}
}
// Validate the referenced game settings exist.
let exists = IGameSettingsSourceDispatcher {
contract_address: self.settings_source.read(),
}
.settings_exist(config.settings_id);
assert(exists, Errors::SETTINGS_MISSING);
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…+ optional item set
Collapse create_objective + create_item_set_objective into ONE endpoint and
generalize an objective from a single check to a CONJUNCTION:
create_objective(name, description, settings_id,
conditions: Array<Condition>, // scalars/single-item, all ANDed
items: Option<Array<u8>>) // "own ALL these" (held anywhere)
An objective is complete iff settings match AND every condition passes AND (when
present) the adventurer holds every item in the set. This makes:
- a simple goal ("reach level 10") one condition,
- a composite goal ("score + gold + holds an item") several conditions,
- "own this whole set of items" the objective's `items` list (as an Option),
all through a single entrypoint — no separate item-set metrics or endpoint.
- interface: `Condition { metric, comparator, target, aux }` (settings_id moves to
the objective); new getters get_objective_settings_id / _conditions / _items.
Drops the ItemSet* metrics.
- oracle_lib: `evaluate` -> `evaluate_condition`; add `holds_all_items` (empty set
vacuously true, zero id never held).
- oracle: per-objective conditions + item-set storage; completed_objective ANDs
them; objectives_details renders one row per condition + an item count.
Supersedes the item-set-metric approach (PR #33). 25 snforge tests (composite,
item-set, items-only, empty-objective + zero-id guards). scarb build + fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Updated (commit ca080f0): objectives are now a single unified endpoint — |
Layer 1 (the foundation) of the beast-mode achievement system: a reusable adventurer oracle — a general objective checker on Death Mountain adventurer NFTs. The tournament validator becomes a thin wrapper on top later; the beast achievements promotion is the first consumer.
What it does
create_objective(config) → u32(owner-gated) +completed_objective(token_id, objective_id) → bool.IMinigameObjectives/IMinigameObjectivesDetails(registers the same SRC5 id) — a drop-in objective provider that generalizes the score-onlygame_token.cairoimpl to any adventurer metric.Schema
Metric:xp, level, gold, health, beast_health, stat_upgrades_available, all 7 stats (strength…luck), and item checks (ItemHeldAnywhere / ItemEquipped / ItemInBag / ItemGreatness). Adding a scalar metric is a one-liner inoracle_lib::scalar_value.Comparator:AtLeast, AtMost, Equal, GreaterThan, LessThan, NotEqual.ObjectiveConfig { settings_id, metric, comparator, target, aux }—targetis the threshold (or item id for item checks);auxis the greatness threshold forItemGreatness.State reads + settings validation
completed_objective(1) requires the objective to exist, (2) reads the token's mintedsettings_idand requires it equals the objective's (mismatch →false), (3) fetches live(Adventurer, Bag), (4) evaluates via pureoracle_lib. Three constructor sources map to prod contracts: GameCoreload_assets(stat-boosted adventurer + bag, soLuck/boosted stats are correct), Denshokansettings_id(token_id), GameTokensettings_exist.Tests
scarb buildclean;snforge test19 passed (scalars, stats, all comparators, equipped/bag/held-anywhere/greatness item checks, create/permissioning, settings-id mismatch → false, unknown-objective → false, batch, ownership, + 2 fuzz);scarb fmt --checkclean.Follow-up before mainnet (recommended)
types.cairomirrors the game'sAdventurer/Bagstructs by Serde field order (to avoid a Scarb 2.15/2.16 + Ekubo dependency clash). The mocks share those mirrors, so they can't catch a layout drift vs the real game contract. A fork test against a live adventurer (decodeload_assetsoutput on mainnet) would lock the real Serde layout end-to-end — worth adding before this gates real rewards.Scoped to the oracle only; the validator adapter, reward campaigns, backend worker, and client achievements panel are subsequent phases.
🤖 Generated with Claude Code