Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
19 changes: 19 additions & 0 deletions packages/interfaces/src/distribution.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,23 @@ pub enum Distribution {
Exponential: u16,
Uniform,
Custom: Span<u16>,
/// Geometric decay as a rational ratio `(a, b)`: each position receives
/// `b / a` of the one above it, so `W(p) = a^(n-p) * b^(p-1)`. Requires
/// `a > b > 0` — e.g. `(10, 7)` is "each place gets 70% of the previous".
///
/// Unlike `Exponential` — which is a power law, and whose winner share
/// falls off as roughly `(k+1)/n` — a geometric curve's shape does not
/// depend on the size of the field: first place takes about `1 - b/a` of
/// the pool whether there are 10 paid places or 100. That is the shape a
/// headline first prize actually needs, and no `Exponential` weight
/// produces it over a large field.
///
/// The trade is reach: the weights span `(a/b)^n`, so the representable
/// field size shrinks as the ratio gets finer. See
/// `max_geometric_payouts`.
///
/// NOTE: appended deliberately. Serde indices are positional, so inserting
/// this anywhere earlier would silently reinterpret every stored and
/// indexed distribution.
Geometric: (u16, u16),
}
14 changes: 12 additions & 2 deletions packages/metagame/src/entry_fee/entry_fee_store.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use game_components_utilities::distribution::packed_shares::{
calculate_slot_position,
};
use game_components_utilities::distribution::structs::{
DIST_TYPE_CUSTOM, DIST_TYPE_EXPONENTIAL, DIST_TYPE_LINEAR, DIST_TYPE_UNIFORM,
PackedDistribution,
DIST_TYPE_CUSTOM, DIST_TYPE_EXPONENTIAL, DIST_TYPE_GEOMETRIC, DIST_TYPE_LINEAR,
DIST_TYPE_UNIFORM, PackedDistribution,
};
use starknet::ContractAddress;
use crate::entry_fee::store::Store;
Expand Down Expand Up @@ -98,6 +98,15 @@ pub impl EntryFeeStoreImpl<T, +Store<T>, +Drop<T>> of EntryFeeStoreTrait<T> {
(Option::Some(Distribution::Exponential(packed_dist.dist_param)), packed_dist.positions)
} else if packed_dist.dist_type == DIST_TYPE_UNIFORM {
(Option::Some(Distribution::Uniform), packed_dist.positions)
} else if packed_dist.dist_type == DIST_TYPE_GEOMETRIC {
(
Option::Some(
Distribution::Geometric(
(packed_dist.dist_param / 256, packed_dist.dist_param % 256),
),
),
packed_dist.positions,
)
} else {
// DIST_TYPE_CUSTOM — return with empty shares span. Loading the
// full array is O(N/15) storage reads and is only needed for UI
Expand Down Expand Up @@ -223,6 +232,7 @@ pub impl EntryFeeStoreImpl<T, +Store<T>, +Drop<T>> of EntryFeeStoreTrait<T> {
Distribution::Exponential(w) => (DIST_TYPE_EXPONENTIAL, *w),
Distribution::Uniform => (DIST_TYPE_UNIFORM, 0_u16),
Distribution::Custom(_) => (DIST_TYPE_CUSTOM, 0_u16),
Distribution::Geometric((a, b)) => (DIST_TYPE_GEOMETRIC, *a * 256 + *b),
},
};
// For Custom, paid places are defined by the shares array length;
Expand Down
10 changes: 10 additions & 0 deletions packages/metagame/src/prize/structs.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub const PAYOUT_TYPE_LINEAR: u8 = 1;
pub const PAYOUT_TYPE_EXPONENTIAL: u8 = 2;
pub const PAYOUT_TYPE_UNIFORM: u8 = 3;
pub const PAYOUT_TYPE_CUSTOM: u8 = 4;
pub const PAYOUT_TYPE_GEOMETRIC: u8 = 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exercise the new payout type in the packing tests.

test_fuzz_realistic_payout_types treats only values 0-4 as valid and uses % 5. It never exercises PAYOUT_TYPE_GEOMETRIC == 5. Include type 5 in the fuzz domain and add an API-level round-trip for Distribution::Geometric((10, 7)).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/metagame/src/prize/structs.cairo` at line 27, Update
test_fuzz_realistic_payout_types to treat PAYOUT_TYPE_GEOMETRIC as valid by
expanding the generated payout-type domain from 0–4 to 0–5. Add an API-level
packing/unpacking round-trip that covers Distribution::Geometric((10, 7)),
preserving the existing round-trip coverage for other distribution variants.


/// Internal packed representation for ERC20 data storage
/// Layout: [amount: 128 bits][payout_type: 8 bits][param: 16 bits][count: 32 bits] = 184 bits
Expand Down Expand Up @@ -131,6 +132,9 @@ fn pack_token_type(token_type: TokenTypeData) -> PackedTokenTypeData {
game_components_utilities::distribution::structs::Distribution::Custom(_) => (
PAYOUT_TYPE_CUSTOM, 0_u16,
),
game_components_utilities::distribution::structs::Distribution::Geometric((
a, b,
)) => (PAYOUT_TYPE_GEOMETRIC, a * 256 + b),
}
},
};
Expand Down Expand Up @@ -170,6 +174,12 @@ fn unpack_token_type(packed_token_type: PackedTokenTypeData) -> TokenTypeData {
Option::Some(
game_components_utilities::distribution::structs::Distribution::Uniform,
)
} else if packed.payout_type == PAYOUT_TYPE_GEOMETRIC {
Option::Some(
game_components_utilities::distribution::structs::Distribution::Geometric(
(packed.param / 256, packed.param % 256),
),
)
} else {
Option::Some(
game_components_utilities::distribution::structs::Distribution::Custom(
Expand Down
4 changes: 4 additions & 0 deletions packages/utilities/src/distribution.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pub mod calculator;
pub mod packed_shares;
pub mod payout;
pub mod structs;

#[cfg(test)]
mod tests;
Loading
Loading