Skip to main content

tengu_api/
xp.rs

1//! Time-weighted staked DOJO balance → XP accrual (pairs with S2P stake in `vesting`).
2//! Activity rewards (PvP, recruit, upgrades, dine, …) use [`add_reward_points_xp`] — same `dojo.xp` / `claim_xp_rewards` pool as Fren-style “points.”
3//! PvP wins use [`xp_from_battle_points_moved`] so XP scales with BP transferred (Fren: ETH rewards tied to point moves).
4
5use crate::consts::XP_STAKE_DIVISOR;
6use crate::state::{Dojo, Stake, Treasury};
7
8/// Reward XP from a PvP win — scales with `points_moved` (Bps [`crate::consts::XP_BATTLE_BP_TO_REWARD_BPS`]).
9#[inline]
10pub fn xp_from_battle_points_moved(points_moved: u64) -> u64 {
11    if points_moved == 0 {
12        return 0;
13    }
14    let bps = crate::consts::XP_BATTLE_BP_TO_REWARD_BPS as u128;
15    ((points_moved as u128).saturating_mul(bps) / 10_000u128) as u64
16}
17
18/// Add reward points toward `claim_xp_rewards` SOL share (PvP wins, recruit, upgrades, first dine per slot, …).
19#[inline]
20pub fn add_reward_points_xp(dojo: &mut Dojo, treasury: &mut Treasury, gain: u64) {
21    if gain == 0 {
22        return;
23    }
24    dojo.xp = dojo.xp.saturating_add(gain);
25    treasury.total_xp = treasury.total_xp.saturating_add(gain);
26}
27
28/// Accrue XP from staked DOJO × elapsed slots. Updates `last_xp_accrual_slot` to `now`.
29pub fn accrue_stake_xp(stake: &mut Stake, dojo: &mut Dojo, treasury: &mut Treasury, now: u64) {
30    if stake.last_xp_accrual_slot == 0 {
31        stake.last_xp_accrual_slot = now;
32        return;
33    }
34    let delta = now.saturating_sub(stake.last_xp_accrual_slot);
35    stake.last_xp_accrual_slot = now;
36    if delta == 0 || stake.balance == 0 {
37        return;
38    }
39    let num = (stake.balance as u128).saturating_mul(delta as u128);
40    let gain = (num / XP_STAKE_DIVISOR) as u64;
41    if gain > 0 {
42        dojo.xp = dojo.xp.saturating_add(gain);
43        treasury.total_xp = treasury.total_xp.saturating_add(gain);
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::xp_from_battle_points_moved;
50
51    #[test]
52    fn xp_from_battle_points_moved_scales() {
53        assert_eq!(xp_from_battle_points_moved(0), 0);
54        // Default BPS 1000: 500 BP → 50 XP
55        assert_eq!(xp_from_battle_points_moved(500), 50);
56    }
57}