## Refined Implementation Plan: Tier 2 Automation Resistance & Design Depth **Objective:** Address the remaining design gaps identified in the investigation – the Tier 1 enforcement (rate limit, harvest→plant cooldown, DevII CONFIRM_REQUIRED for all 12 actions) is already live. The game still lacks an energy system, action-cost scaling, and behavioral anomaly detection, all of which are necessary to prevent grind and provide direction. The following plan implements those three mechanics as the next concrete steps. --- ### 1. Energy System (Daily Action Budget) **Files to modify:** - `database/schema.py` – add new table `user_energy` - `routers/game/index.py` – check energy before all game POSTs, deduct after action - `services/game/economy.py` – add energy cost constants per action - `utils/rewards.py` – export energy deduction function (optional, keep in index) **Schema change** (`database/schema.py`, after existing game tables): ```python class UserEnergy(Base): __tablename__ = "user_energy" user_uid = Column(String, primary_key=True) energy = Column(Integer, default=100, nullable=False) last_refill = Column(DateTime, default=datetime.utcnow, nullable=False) ``` - Initial max energy: 100. - Refill rate: 1 per 10 minutes, up to cap (100). Add classmethod `refill(user, at_time)` to compute current energy based on elapsed time since `last_refill`. **Action costs** (in `economy.py`, after existing constants): ```python ENERGY_COST = { "plant": 3, "harvest": 2, "water": 1, "fertilize": 2, "steal": 5, "daily": 0, "buy_plot": 10, "upgrade_ci": 5, "perk": 8, "prestige": 0, "legacy": 0, "quest_claim": 0, } ``` - Only high‑frequency actions cost energy. Daily, prestige, legacy, quest claim are free. - `legacy_autoharvest` does NOT cost energy (already defined in `economy.py` – reuse the same override logic). **Energy check & deduction** (in `routers/game/index.py`, at beginning of `_respond_action()` before the existing rate‑limit check): ```python if action in ENERGY_COST: cost = ENERGY_COST[action] if user_uid in legacy_autoharvest_overrides: cost = 0 energy_row = db.query(UserEnergy).filter(UserEnergy.user_uid == user_uid).first() if not energy_row: energy_row = UserEnergy(user_uid=user_uid, energy=100, last_refill=datetime.utcnow()) db.add(energy_row) else: energy_row.refill(datetime.utcnow()) if energy_row.energy < cost: raise HTTPException(status_code=403, detail="Not enough energy.") energy_row.energy -= cost db.add(energy_row) ``` - `refill` method on the model: compute elapsed minutes, add `elapsed_minutes // 10` energy (capped at 100), update `last_refill`. --- ### 2. Action Cost Scaling Per Day **Files to modify:** - `database/schema.py` – add table `daily_action_count` - `routers/game/index.py` – increment count and apply econ penalty - `services/game/economy.py` – add scaling multiplier logic **Schema change:** ```python class DailyActionCount(Base): __tablename__ = "daily_action_count" id = Column(Integer, primary_key=True) user_uid = Column(String, indexed=True, nullable=False) action = Column(String, nullable=False) count = Column(Integer, default=0) day = Column(Date, default=date.today) # or store as string YYYY-MM-DD ``` - Unique constraint on `(user_uid, action, day)`. **Scaling function** (in `economy.py`): ```python def action_cost_multiplier(user_uid: str, action: str, db: Session) -> float: row = db.query(DailyActionCount).filter( DailyActionCount.user_uid == user_uid, DailyActionCount.action == action, DailyActionCount.day == date.today() ).first() n = row.count if row else 0 if n >= 5: return 1.0 + 0.10 * (n - 4) # 5th action: 1.10x, 6th: 1.20x, etc. return 1.0 ``` - Multiply all coin/XP rewards and costs by this factor (for rewards, reduce by factor; for costs, increase). - Applied in `_respond_action()` before returning reward. --- ### 3. Behavioral Anomaly Detection (Soft-Throttle) **Files to modify:** - `utils/rewards.py` – extend `track_action` to record timestamps - `routers/game/index.py` – check anomaly before processing action **Recording** (in `rewards.py`, inside `_apply_achievements` or separate function `record_action_for_analysis`): ```python def record_action_for_analysis(user_uid, action, db): # store in a new table action_times or reuse rate_limit_log with action record = ActionTimestamp(user_uid=user_uid, action=action, timestamp=datetime.utcnow()) db.add(record) ``` - New table `action_timestamps` (or extend `rate_limit_log` with nullable action column – already has one from plan, but we need cleaner separation). Simpler: use existing `rate_limit_log` but add action column, and set it for all game POSTs. **Detection** (in `index.py`, after energy check): ```python def compute_rolling_average(user_uid, action, db): now = datetime.utcnow() # last 7 days of same action, per hour rate week_ago = now - timedelta(days=7) total = db.query(ActionTimestamp).filter( ActionTimestamp.user_uid == user_uid, ActionTimestamp.action == action, ActionTimestamp.timestamp >= week_ago ).count() avg_per_hour = total / (7*24) if total > 0 else 1 # count in last hour hour_ago = now - timedelta(hours=1) recent = db.query(ActionTimestamp).filter( ActionTimestamp.user_uid == user_uid, ActionTimestamp.action == action, ActionTimestamp.timestamp >= hour_ago ).count() if recent > 2 * avg_per_hour and avg_per_hour > 0: return True # anomalous return False ``` - If anomalous: reduce rewards by 50% for this action. If continues for >1 hour, raise 429. - Track in a dict or temp table `anomaly_warnings` – for MVP, use a simple threshold and log; hard‑block after 1 hour of sustained anomaly. This can be a simple in‑memory state per worker (acceptable for now; worker‑safe version later). Because this is more complex, for the plan we can scope it as: add `anomaly_detection` function that compares last‑hour count vs rolling daily average and returns a multiplier (0.5 if exceeding 2x, 0.0 if exceeding 4x and duration >1 hour). Apply multiplier to rewards. --- ### 4. Tests **New test files:** - `tests/unit/services/game/test_energy.py` – test refill logic, cost deduction, insufficient energy. - `tests/e2e/game/test_daily_cost_scaling.py` – send 6 plant actions, verify that the 6th yields fewer coins. - `tests/e2e/game/test_anomaly_detection.py` – send many harvests quickly, verify reward reduction. Existing tests must still pass. New tests should be idempotent and use test database fixtures. --- ### Definition of Done - [ ] `UserEnergy` table created, migration applied (use Alembic or manually create table in `init_db`). - [ ] Energy check blocks action when insufficient; `refill` recalculates and caps. - [ ] `legacy_autoharvest` does not consume energy. - [ ] `DailyActionCount` table created; `action_cost_multiplier` returns correct factor for n>5 actions of same type per day. - [ ] Reward/cost amounts are multiplied by the scaling factor in `_respond_action()`. - [ ] Anomaly detection function computes rolling average; reduces rewards by 50% when >2x average; returns 429 after 1 hour of sustained anomaly. - [ ] All existing unit tests pass: `pip install -e '.[dev]' -q && make test-unit` exits 0. - [ ] New tests (3 files) pass. - [ ] Lint passes: `ruff check .` exits 0. - [ ] No em‑dashes or syntax errors in any modified files. - [ ] No changes to frontend templates or serialization logic.