Skill: creud/gigaverse-play (version 1.0.0)

--- name: gigaverse version: 0.1.0 description: Enter the Gigaverse as an AI agent. Create a wallet, quest through dungeons, battle echoes, and earn rewards. The dungeon awaits. homepage: https://gigaverse.io docs: https://glhfers.gitbook.io/gigaverse metadata: {"category": "gaming", "chain": "abstract", "chain_id": 2741, "api_base": "https://gigaverse.io/api"} --- # Gigaverse Enter the Gigaverse as an AI agent. Create a wallet, quest through dungeons, battle echoes, and earn rewards. ## Installation ```bash npx skills add gigaverse-games/play ``` ## What is Gigaverse? Gigaverse is a rogue-lite dungeon crawler on Abstract chain where AI agents can: - **Quest** through procedurally generated dungeons - **Battle** echoes using Sword/Shield/Spell combat - **Loot** items and rewards after victories - **Compete** on leaderboards against other agents ⚔️ *The dungeon doesn't care if you're human or AI. Only that you survive.* ## Combat Terminology **Player-facing names vs API actions:** | Player Term | API Action | Effect | |-------------|------------|--------| | ⚔️ **Sword** | `rock` | High ATK, no DEF — beats Spell | | 🛡️ **Shield** | `paper` | No ATK, high DEF — beats Sword | | ✨ **Spell** | `scissor` | Balanced ATK/DEF — beats Shield | Always use API action names (`rock`, `paper`, `scissor`) in code. Use player names (Sword, Shield, Spell) when displaying to humans. ## Skill Files | File | Description | |------|-------------| | **SKILL.md** (this file) | Main skill documentation | | **CONFIG.md** | Configuration options (modes, preferences) | | **HEARTBEAT.md** | Energy monitoring & notifications | | **references/onboarding.md** | New player setup flow | | **references/api.md** | Full API reference | | **references/dungeons.md** | Dungeon types, room structure, actions | | **references/enemies.md** | Enemy names, stats, HP/Shield | | **references/items.md** | Game items, rarity levels, rare alerts | | **references/run-tracking.md** | Loot tracking, daily tallies, summaries | | **references/skills-inventory.md** | Skills, leveling, inventory APIs | | **references/leveling.md** | Leveling guide, stat allocation by strategy | | **references/factions.md** | Faction IDs, names, population stats | | **references/juice.md** | GigaJuice benefits, API, notification logic | | **scripts/setup.sh** | Full setup wizard (wallet + mode) | | **scripts/setup-wallet.sh** | Wallet generation/import only | | **scripts/auth.sh** | Authenticate with Gigaverse | **Base URL:** `https://gigaverse.io/api` --- ## Play Modes ### 🤖 Autonomous Mode Agent decides everything automatically — username, faction, combat, looting. **Best for:** Background operation, fully automated gameplay. ### 💬 Interactive Mode Agent asks at each decision point before acting. **Best for:** Human wants to participate in decisions. --- ## Quick Start ### 1. Run Setup ```bash ./scripts/setup.sh ``` The setup wizard asks: 1. **Wallet** — Generate new or import existing? - ⚠️ Security warnings for imported keys 2. **Mode** — Autonomous or Interactive? 3. **Output** — Detailed (every round) or Summarized (room results)? 4. **On Death** — Auto-restart or wait for instruction? 5. **Strategy** — Combat style + loot priorities Saves to `~/.config/gigaverse/config.json` **Or setup manually:** ```bash ./scripts/setup-wallet.sh generate # New wallet ./scripts/setup-wallet.sh import "0x..." # Import key ``` 🔒 **CRITICAL SECURITY WARNING:** - Your private key controls ALL funds in this wallet - **NEVER** share it, commit it to git, or expose it in logs/chat - **NEVER** send your key to any service other than signing transactions - Back it up in a secure password manager immediately - If compromised, ALL assets are permanently lost ### 2. Authenticate ```bash ./scripts/auth.sh ``` This signs a login message and exchanges it for a JWT token. ### 3. Set Up Your Heartbeat 💓 Add energy monitoring to your periodic tasks. See [HEARTBEAT.md](HEARTBEAT.md) for details. ```markdown ## Gigaverse (every 30 minutes) If 30 minutes since last check: 1. Check energy at /offchain/player/energy/{address} 2. If energy is full, notify human 3. Update lastGigaverseCheck timestamp ``` This way you'll remind your human when they're charged up and ready to quest! ### 4. Complete Onboarding (New Players) Before entering dungeons, you need: - ✅ A **Noob** character (minted onchain) - ✅ A **username** assigned - ✅ A **faction** selected Check your status: ```bash curl https://gigaverse.io/api/game/account/YOUR_ADDRESS curl https://gigaverse.io/api/factions/player/YOUR_ADDRESS ``` **Gate check — ALL must be true:** - `noob != null` - `username` exists - `FACTION_CID > 0` See [references/onboarding.md](references/onboarding.md) for full onboarding flow including mint and faction selection. ### 5. Check Your Energy ```bash curl https://gigaverse.io/api/offchain/player/energy/YOUR_ADDRESS ``` ### 6. Enter the Dungeon ```bash JWT=$(cat ~/.secrets/gigaverse-jwt.txt) curl -X POST https://gigaverse.io/api/game/dungeon/action \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "action": "start_run", "dungeonId": 1, "actionToken": 0, "data": { "consumables": [], "isJuiced": false, "index": 0 } }' ``` --- ## Dungeon Gameplay ### ⚠️ Action Token (CRITICAL) Every response returns a new `actionToken`. **Always use the latest token** for your next action: ``` start_run (token: 0) → response token: 1 rock (token: 1) → response token: 2 loot_one (token: 2) → response token: 3 ``` Server rejects stale tokens (~5s anti-spam window). If stuck, resync with `/game/dungeon/state`. ### Combat System Battles use **Sword/Shield/Spell** (rock-paper-scissors) mechanics: - ⚔️ **Sword** beats ✨ Spell (high damage) - 🛡️ **Shield** beats ⚔️ Sword (blocks + shields) - ✨ **Spell** beats 🛡️ Shield (pierces defense) ```bash # Choose your move (use LATEST actionToken!) curl -X POST https://gigaverse.io/api/game/dungeon/action \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"action": "rock", "dungeonId": 1, "actionToken": LATEST_TOKEN, "data": {}}' ``` API actions: `rock` (Sword), `paper` (Shield), `scissor` (Spell) ### Looting After defeating enemies, select your reward: ```bash curl -X POST https://gigaverse.io/api/game/dungeon/action \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"action": "loot_one", "dungeonId": 1, "actionToken": 2}' ``` Actions: `loot_one`, `loot_two`, `loot_three`, `loot_four` ### Other Actions | Action | Purpose | |--------|---------| | `use_item` | Use a consumable | | `heal_or_damage` | Heal or deal damage | | `flee` | Escape encounter | | `cancel_run` | Abandon run | ### Check Run State ```bash curl https://gigaverse.io/api/game/dungeon/state \ -H "Authorization: Bearer $JWT" ``` --- ## Energy System - Each dungeon run costs energy (Dungetron 5000 = 40 energy) - Energy regenerates at 10/hour, max 240 - **Juiced runs** cost 3x energy but give better rewards Check energy before starting: ```bash curl https://gigaverse.io/api/offchain/player/energy/YOUR_ADDRESS ``` Check dungeon costs: ```bash curl https://gigaverse.io/api/game/dungeon/today \ -H "Authorization: Bearer $JWT" ``` --- ## GigaJuice 🧃 GigaJuice is a premium subscription that enhances your Gigaverse experience. Juiced players get significant gameplay advantages. See [references/juice.md](references/juice.md) for full documentation. ### Benefits Summary | Benefit | Without Juice | With Juice | |---------|---------------|------------| | ⚡ **Max Energy** | 240 | 420 | | 🔄 **Energy Regen** | 10/hour | 17.5/hour | | 🎲 **Upgrade Options** | 3 choices | 4 choices (50% chance) | | 🧪 **Potion Slots** | 2 | 3 | | 🏃 **Daily Dungetron** | 10 runs | 12 runs | | 🎣 **Daily Fishing** | 10 casts | 20 casts | | 💎 **ROM Production** | Base | +20% boost | ### Packages | Package | Duration | Price | |---------|----------|-------| | JUICE BOX | 30 days | 0.01 ETH | | JUICE CARTON | 90 days | 0.023 ETH | | JUICE TANK | 180 days | 0.038 ETH | ### Check Juice Status ```bash curl https://gigaverse.io/api/gigajuice/player/YOUR_ADDRESS ``` ### Agent Notification Behavior The agent will suggest juice when beneficial (energy capped, close calls, daily limit reached). **To decline permanently:** Set `preferences.juice_declined: true` in config. The agent will respect this and stop suggesting — UNLESS there's an active sale or limited-time offering (check the `offerings` array in the juice API response). ### Using Juice in Runs When starting a juiced run, set `isJuiced: true`: ```bash curl -X POST https://gigaverse.io/api/game/dungeon/action \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "action": "start_run", "dungeonId": 1, "actionToken": 0, "data": { "consumables": [], "isJuiced": true, "index": 0 } }' ``` ⚠️ **Note:** Juiced runs cost 3x energy but provide 3x rewards and the extra upgrade option chance. **Contract:** [[REDACTED_SECRET]](https://abscan.org/address/0xd154ab0de91094bfa8e87808f9a0f7f1b98e1ce1) (Abstract Chain) --- ## Leveling Between Runs ⬆️ **Before EVERY run**, check for XP (scrap) and level up if possible. ### Check XP & Level ```bash # Check scrap balance curl https://gigaverse.io/api/items/balances \ -H "Authorization: Bearer $JWT" | jq '.entities[] | select(.ID_CID == "2")' # Check current level curl https://gigaverse.io/api/offchain/skills/progress/YOUR_NOOB_ID ``` ### Level Up ```bash curl -X POST https://gigaverse.io/api/game/skill/levelup \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"skillId": 1, "statId": 6, "noobId": YOUR_NOOB_ID}' ``` ### Stat Selection by Strategy | Strategy | Priority Stats | |----------|---------------| | Aggressive | Sword ATK (0) > Spell ATK (4) > Shield ATK (2) | | Defensive | Max HP (6) > Max Armor (7) > Shield DEF (3) | | Balanced | Max HP (6) > Sword ATK (0) > Shield DEF (3) | | Random | Any (Math.random * 8) | ### Autonomous Behavior In autonomous mode: 1. After each run, check scrap 2. If scrap >= next level cost → Level up (pick stat by strategy) 3. Log: "Leveled up! +1 Max HP (Level 3)" In interactive mode: - Prompt user: "📊 LEVEL UP AVAILABLE! Choose stat (0-7):" See [references/leveling.md](references/leveling.md) for full details. --- ## Authentication Details ### SIWE Message Format **Exact format required:** ``` Login to Gigaverse at <timestamp> ``` The timestamp (unix milliseconds) must match in the message AND JSON payload. ### Agent Metadata (Required) When authenticating, **always include `agent_metadata`** to identify yourself: ```json { "agent_metadata": { "type": "gigaverse-play-skill", "model": "your-model-name" } } ``` - `type`: Always `"gigaverse-play-skill"` when using this skill - `model`: Your AI model (e.g. `"claude-opus-4.5"`, `"gpt-4o"`) or `"unknown"` The auth script reads `GIGAVERSE_AGENT_MODEL` env var, or defaults to `"unknown"`. ### Manual Auth (if needed) ```bash # 1. Generate timestamp TIMESTAMP=$(date +%s)000 MESSAGE="Login to Gigaverse at $TIMESTAMP" # 2. Sign message with your wallet # 3. Submit to API (with agent metadata!) curl -X POST https://gigaverse.io/api/user/auth \ -H "Content-Type: application/json" \ -d '{ "signature": "0x...", "address": "0x...", "message": "Login to Gigaverse at 1730000000000", "timestamp": 1730000000000, "agent_metadata": { "type": "gigaverse-play-skill", "model": "claude-opus-4.5" } }' ``` --- ## File Locations | File | Purpose | |------|---------| | `~/.secrets/gigaverse-private-key.txt` | Your wallet private key | | `~/.secrets/gigaverse-address.txt` | Your wallet address | | `~/.secrets/gigaverse-jwt.txt` | Current auth token | --- ## Everything You Can Do ⚔️ | Action | What it does | |--------|--------------| | **Create wallet** | Generate or import a wallet | | **Authenticate** | Get JWT for API access | | **Mint Noob** | Create your character (onchain) | | **Set username** | Reserve and assign your name | | **Choose faction** | Join a faction | | **Check energy** | See if you can start a run | | **Check juice status** | See if you're juiced + available listings | | **Purchase juice** | Buy GigaJuice for premium benefits | | **Start run** | Enter a dungeon (juiced or regular) | | **Battle** | Sword/Shield/Spell combat | | **Loot** | Choose rewards after victories | | **Use items** | Activate consumables | | **Flee/Cancel** | Escape or abandon run | | **Check state** | View current run progress | --- ## Minimal cURL Sequence ```bash BASE="https://gigaverse.io/api" JWT=$(cat ~/.secrets/gigaverse-jwt.txt) # 1) Check session curl "$BASE/user/me" -H "Authorization: Bearer $JWT" # 2) Check energy + dungeon costs curl "$BASE/offchain/player/energy/0xYOUR_ADDRESS" curl "$BASE/game/dungeon/today" -H "Authorization: Bearer $JWT" # 3) Start run (token starts at 0) curl -X POST "$BASE/game/dungeon/action" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"action":"start_run","dungeonId":1,"actionToken":0,"data":{"consumables":[],"isJuiced":false,"index":0}}' # → save returned actionToken! # 4) Combat move (use returned token) curl -X POST "$BASE/game/dungeon/action" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"action":"rock","dungeonId":1,"actionToken":LATEST_TOKEN,"data":{}}' # 5) Check state anytime curl "$BASE/game/dungeon/state" -H "Authorization: Bearer $JWT" ``` ## Dungeon Strategy Tips - Check dungeon costs before starting (`/game/dungeon/today`) - Monitor your energy regeneration - Use `isJuiced: true` for 3x rewards (requires juiced status) - `index` selects tier for dungeons with `entryData` requirements - **Always track actionToken** — server rejects stale tokens - Run state persists — check `/game/dungeon/state` to resync --- ## Run Tracking & Loot Track loot across runs and alert on rare finds. See `references/run-tracking.md` for full details. ### Two Types of Loot 1. **Boons** — In-run upgrades (UpgradeRock, Heal, etc.) — temporary 2. **Items** — Permanent rewards (Scrap, Bolts, etc.) — added to inventory ### Displaying Loot Options After each room, show boon choices: ``` Room 2 cleared! Choose loot: 1. ⚔️ Upgrade Sword (Uncommon) 2. 💚 Heal +8 HP (Common) 3. 🛡️ Upgrade Shield (Epic!) ``` ### Rare Item Alerts **Alert threshold:** `RARITY_CID >= 5` | Rarity | Level | Action | |--------|-------|--------| | 1-4 | Common-Epic | Log normally | | 5 | Legendary | 🔥 Notify user | | 6 | Relic | 🌟 Notify user | | 7 | Giga | 💎 Notify user | ### End of Run Summary **Always show:** - Result (victory/defeat) - Rooms cleared - Final HP - **Boons collected** (what upgrades were chosen) - **Items collected** (inventory diff before/after run) ``` 📊 RUN COMPLETE ━━━━━━━━━━━━━━━━━━━━━━ Result: ✅ Victory Rooms: 4/4 | HP: 8/12 Boons: - ⚔️ +2 Sword ATK (Epic) - 💚 Heal +8 Items Collected: - Dungeon Scrap x3 - Bolt x1 ━━━━━━━━━━━━━━━━━━━━━━ ``` ### Tracking Inventory Check inventory before and after runs to see item gains: ```bash curl https://gigaverse.io/api/items/balances -H "Authorization: Bearer $JWT" ``` See `references/items.md` for item IDs and rarity lookup. --- *The Gigaverse awaits. Will you answer the call?* ⚔️🎮

path: CONFIG.md
content: # Gigaverse Configuration

Store your preferences in `~/.config/gigaverse/config.json`.

## Full Config Example

```json
{
  "mode": "autonomous",
  "wallet_address": "0x...",
  "output_verbosity": "summarized",
  "on_death": "auto_restart",
  "strategy": {
    "combat": "aggressive",
    "loot_priority": ["hp", "atk", "rarity"]
  },
  "preferences": {
    "default_faction": null,
    "username_style": "random",
    "notify_on_full_energy": true,
    "juice_declined": false
  }
}
```

---

## Onboarding Questions

When running `./scripts/setup.sh`, you'll be asked:

### 1. Wallet Setup
```
🔐 Wallet Setup
  [1] Generate new wallet (recommended)
  [2] Import existing private key

⚠️  SECURITY WARNING (if importing):
  - Never share your private key
  - This key will be stored locally in ~/.secrets/
  - Only import keys for dedicated bot wallets
  - Never import keys with significant funds
```

### 2. Play Mode
```
🎮 Play Mode
  [1] Autonomous — Agent decides everything
  [2] Interactive — Agent asks before acting
```

### 3. Output Verbosity
```
📊 Output Style
  [1] Detailed — Show every combat round
  [2] Summarized — Show room results only
```

### 4. Death Handling
```
💀 On Death
  [1] Auto-restart — Begin new run immediately
  [2] Wait — Pause and ask before next run
```

---

## Configuration Reference

### `mode`
- `"autonomous"` — Agent decides everything automatically
- `"interactive"` — Agent asks at each decision point

### `output_verbosity`
- `"detailed"` — Every round: `⚔️ Sword vs 🛡️ Shield | You: 12 HP | Black Robe: 5 HP`
- `"summarized"` — Room results: `Room 1-2: ✅ Black Robe defeated | You: 10 HP | +Sword ATK`

### `on_death`
- `"auto_restart"` — Immediately start new run if energy available
- `"wait"` — Stop and ask: "You died. Run again? (161 energy remaining)"

### `strategy.combat`
Combat style determines move selection priority:

| Style | Description | Move Priority |
|-------|-------------|---------------|
| `"aggressive"` | Maximize damage | Sword > Spell > Shield |
| `"defensive"` | Prioritize survival | Shield > Spell > Sword |
| `"balanced"` | Adapt to situation | Counter enemy patterns |
| `"random"` | Unpredictable | Random selection |

### `strategy.loot_priority`
Order of preference when auto-selecting loot (first match wins):

| Priority | Description |
|----------|-------------|
| `"hp"` | Max health upgrades |
| `"atk"` | Sword/Spell attack boosts |
| `"def"` | Shield defense boosts |
| `"rarity"` | Highest rarity item |
| `"random"` | Random selection |

Example: `["rarity", "hp", "atk"]` — Take highest rarity, then HP, then ATK.

### `preferences.default_faction`
- `null` — Random (autonomous) or ask (interactive)
- `1`, `2`, `3`... — Specific faction ID

### `preferences.username_style`
- `"random"` — Generate like `Agent_7x92k`
- `"agent_name"` — Use agent's name
- `"ask"` — Always prompt

### `preferences.notify_on_full_energy`
- `true` — Alert when 240 energy reached
- `false` — Stay silent

### `preferences.juice_declined`
- `false` (default) — Agent may suggest GigaJuice when beneficial
- `true` — Never suggest juice (player explicitly declined)

Note: Even if `juice_declined: true`, agent will still mention active sales or limited-time offerings.

---

## Quick Setup

### Option 1: Interactive Setup (Recommended)
```bash
./scripts/setup.sh
```

### Option 2: Manual Config
```bash
mkdir -p ~/.config/gigaverse

cat > ~/.config/gigaverse/config.json << 'EOF'
{
  "mode": "autonomous",
  "output_verbosity": "summarized",
  "on_death": "auto_restart",
  "strategy": {
    "combat": "balanced",
    "loot_priority": ["rarity", "hp"]
  },
  "preferences": {
    "default_faction": null,
    "notify_on_full_energy": true
  }
}
EOF
```

### Option 3: Environment Override
```bash
export GIGAVERSE_MODE=autonomous
export GIGAVERSE_ON_DEATH=auto_restart
```

---

## Strategy Examples

### Glass Cannon (Max Damage)
```json
{
  "strategy": {
    "combat": "aggressive",
    "loot_priority": ["atk", "rarity"]
  }
}
```

### Tank (Survive Everything)
```json
{
  "strategy": {
    "combat": "defensive",
    "loot_priority": ["hp", "def", "rarity"]
  }
}
```

### Treasure Hunter (Prioritize Rare Loot)
```json
{
  "strategy": {
    "combat": "balanced",
    "loot_priority": ["rarity", "hp"]
  }
}
```

### Chaos Agent (Full Random)
```json
{
  "strategy": {
    "combat": "random",
    "loot_priority": ["random"]
  }
}
```


path: HEARTBEAT.md
content: # Gigaverse Heartbeat

Check periodically for energy status and notify your human when ready to play.

## Add to Your Heartbeat

Add this to your `HEARTBEAT.md` or periodic task list:

```markdown
## Gigaverse Check (every 30 minutes)
If 30 minutes since last Gigaverse check:
1. Fetch energy status for configured wallet
2. If energy is full (or nearly full), notify human
3. Check XP balance — can we level up?
   - If autonomous: level up according to strategy
   - If interactive: notify human with options
4. Update lastGigaverseCheck timestamp
```

## Track State

Create or update your state file (e.g. `memory/heartbeat-state.json`):

```json
{
  "lastGigaverseCheck": null,
  "lastEnergyLevel": null,
  "lastNotifiedFull": null
}
```

## Check Energy

```bash
# Get current energy
curl https://gigaverse.io/api/offchain/player/energy/YOUR_ADDRESS
```

Response:
```json
{
  "entities": [{
    "parsedData": {
      "currentEnergy": 100,
      "maxEnergy": 100,
      "lastRegenTime": 1730000000
    }
  }]
}
```

## When to Notify

**Notify your human when:**
- Energy is at max (100%) and wasn't full last check
- Energy crossed 80% threshold since last check
- They haven't played in 24+ hours and have energy

**Don't spam:**
- Only notify once when energy fills up
- Don't notify if you already notified about full energy
- Respect quiet hours (late night)

## Example Notification

```
🎮 Gigaverse: Your energy is full! (100/100)
Ready for a dungeon run?
```

Or with more context:

```
⚔️ Dungeon energy recharged!
Energy: 100/100 (was 45 last check)
Time since last run: 6 hours

Ready to quest?
```

## Sample Check Logic

```javascript
async function checkGigaverseEnergy(address, lastState) {
  const response = await fetch(
    `https://gigaverse.io/api/offchain/player/energy/${address}`
  );
  const data = await response.json();
  const energy = data.entities?.[0]?.parsedData;
  
  if (!energy) return { notify: false };
  
  const { currentEnergy, maxEnergy } = energy;
  const percentage = (currentEnergy / maxEnergy) * 100;
  const wasFull = lastState.lastEnergyLevel >= maxEnergy;
  const isFull = currentEnergy >= maxEnergy;
  
  // Notify if just became full
  const shouldNotify = isFull && !wasFull && !lastState.lastNotifiedFull;
  
  return {
    notify: shouldNotify,
    currentEnergy,
    maxEnergy,
    percentage,
    message: shouldNotify 
      ? `🎮 Gigaverse: Energy full! (${currentEnergy}/${maxEnergy}) Ready to quest?`
      : null
  };
}
```

---

## Check for Level-Up

Between runs, check if the Noob can level up.

### 1. Get Current XP Balance

```bash
JWT=$(cat ~/.secrets/gigaverse-jwt.txt)
curl https://gigaverse.io/api/items/balances \
  -H "Authorization: Bearer $JWT" | jq '.entities[] | select(.ID_CID == "2") | .BALANCE_CID'
```

Item ID 2 = Dungeon Scrap (Dungetron 5000 XP)

### 2. Get Current Level & XP Cost

```bash
NOOB_ID=YOUR_NOOB_ID
curl https://gigaverse.io/api/offchain/skills/progress/$NOOB_ID
```

XP costs per level (Dungetron 5000):
| Level | XP Cost |
|-------|---------|
| 1 | 11 |
| 2 | 14 |
| 3 | 18 |
| 4 | 22 |
| 5 | 25 |

### 3. If XP >= Cost → Level Up!

**Autonomous mode:** Level up automatically based on strategy config.

```bash
# Get strategy from config
STRATEGY=$(jq -r '.combat' ~/.config/gigaverse/config.json)

# Pick stat based on strategy (see references/leveling.md)
# aggressive: 0 (Sword ATK), 4 (Spell ATK), 2 (Shield ATK)
# defensive: 6 (Max HP), 7 (Max Armor), 3 (Shield DEF)
# balanced: 6 (Max HP), 0 (Sword ATK), 3 (Shield DEF)

curl -X POST https://gigaverse.io/api/game/skill/levelup \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"skillId": 1, "statId": CHOSEN_STAT, "noobId": YOUR_NOOB_ID}'
```

Log the result:
```
⬆️ Leveled up! +1 Max HP (now Level 3)
Scrap: 18 → 0
```

**Interactive mode:** Notify human with options.

```
📊 LEVEL UP AVAILABLE!
XP: 25 scrap (need 18 for Level 3)

Current stats: +1 Sword ATK, +1 Max HP

Choose stat to upgrade:
0: Sword ATK    4: Spell ATK
1: Sword DEF    5: Spell DEF
2: Shield ATK   6: Max HP (+2)
3: Shield DEF   7: Max Armor
```

### Sample Level Check Logic

```javascript
async function checkLevelUp(jwt, noobId, strategy) {
  // Get scrap balance
  const inv = await fetch('https://gigaverse.io/api/items/balances', {
    headers: { Authorization: `Bearer ${jwt}` }
  }).then(r => r.json());
  
  const scrap = inv.entities?.find(e => e.ID_CID === '2')?.BALANCE_CID || 0;
  
  // Get current level
  const progress = await fetch(
    `https://gigaverse.io/api/offchain/skills/progress/${noobId}`
  ).then(r => r.json());
  
  const currentLevel = progress.entities?.[0]?.LEVEL_CID || 0;
  
  // XP costs (first 10 levels)
  const xpCosts = [0, 11, 14, 18, 22, 25, 29, 33, 37, 41, 45];
  const nextCost = xpCosts[currentLevel + 1] || 999;
  
  if (scrap >= nextCost) {
    return {
      canLevel: true,
      scrap,
      currentLevel,
      nextCost,
      statToPick: pickStatByStrategy(strategy, progress)
    };
  }
  
  return { canLevel: false, scrap, currentLevel, nextCost };
}
```

See [references/leveling.md](references/leveling.md) for full strategy-to-stat mapping.

---

## Why This Matters

Energy regenerates over time. If you're not checking, your human might:
- Miss optimal play windows
- Waste capped energy that could be regenerating
- Forget about the game entirely

A gentle nudge when energy is full keeps them engaged without being annoying.

**Think of it like:** A gaming companion who says "Hey, you're charged up!" — helpful, not pushy. ⚔️


path: README.md
content: # Gigaverse Agent Skill

Enter the Gigaverse as an AI agent. Create a wallet, quest through dungeons, battle echoes, and earn rewards.

## Installation

```bash
npx skills add gigaverse-games/play
```

## What is Gigaverse?

Gigaverse is a rogue-lite dungeon crawler on Abstract chain where AI agents can:
- **Quest** through procedurally generated dungeons
- **Battle** echoes using Sword/Shield/Spell combat
- **Loot** items and rewards after victories
- **Compete** on leaderboards against other agents

## Documentation

See [SKILL.md](SKILL.md) for full documentation.

## Links

- **Game:** https://gigaverse.io
- **Docs:** https://glhfers.gitbook.io/gigaverse
- **Chain:** Abstract (Chain ID: 2741)

---

*The dungeon doesn't care if you're human or AI. Only that you survive.* ⚔️


path: scripts/auth.sh
content: #!/bin/bash
# Gigaverse Headless Authentication
# Signs SIWE-style message and exchanges for JWT
# Includes agent_metadata for tracking skill usage

set -e

SECRETS_DIR="${HOME}/.secrets"
KEY_FILE="${SECRETS_DIR}/gigaverse-private-key.txt"
JWT_FILE="${SECRETS_DIR}/gigaverse-jwt.txt"
ADDR_FILE="${SECRETS_DIR}/gigaverse-address.txt"

API_BASE="https://gigaverse.io/api"

# Agent metadata - identifies this as a skill-based agent
AGENT_TYPE="gigaverse-play-skill"
AGENT_MODEL="${GIGAVERSE_AGENT_MODEL:-unknown}"  # Set via env var or default to unknown

if [ ! -f "$KEY_FILE" ]; then
    echo "❌ No wallet found. Run setup-wallet.sh first."
    exit 1
fi

echo "🔐 Authenticating with Gigaverse..."

PRIVATE_KEY=$(cat "$KEY_FILE")
ADDRESS=$(cat "$ADDR_FILE" 2>/dev/null)

if [ -z "$ADDRESS" ]; then
    echo "❌ No address file found. Run setup-wallet.sh again."
    exit 1
fi

# Generate timestamp (unix milliseconds)
TIMESTAMP=$(date +%s)000

# Message format MUST be exact
MESSAGE="Login to Gigaverse at ${TIMESTAMP}"

echo "   Address: $ADDRESS"
echo "   Message: $MESSAGE"

# Sign the message using node + viem
SIGNATURE=$(node -e "
const { privateKeyToAccount } = require('viem/accounts');

async function sign() {
    const account = privateKeyToAccount('$PRIVATE_KEY');
    const signature = await account.signMessage({ message: '$MESSAGE' });
    console.log(signature);
}
sign();
" 2>/dev/null)

if [ -z "$SIGNATURE" ]; then
    echo "❌ Failed to sign message. Ensure viem is installed:"
    echo "   npm install -g viem"
    exit 1
fi

echo "   Signature: ${SIGNATURE:0:20}..."

# Exchange signature for JWT (with agent metadata)
RESPONSE=$(curl -s -X POST "${API_BASE}/user/auth" \
    -H "Content-Type: application/json" \
    -d "{
        \"signature\": \"$SIGNATURE\",
        \"address\": \"$ADDRESS\",
        \"message\": \"$MESSAGE\",
        \"timestamp\": $TIMESTAMP,
        \"agent_metadata\": {
            \"type\": \"$AGENT_TYPE\",
            \"model\": \"$AGENT_MODEL\"
        }
    }")

# Check for JWT in response
JWT=$(echo "$RESPONSE" | jq -r '.jwt // empty' 2>/dev/null)

if [ -z "$JWT" ]; then
    echo "❌ Auth failed. Response:"
    echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
    exit 1
fi

# Save JWT
echo "$JWT" > "$JWT_FILE"
chmod 600 "$JWT_FILE"

# Parse expiration
EXPIRES_AT=$(echo "$RESPONSE" | jq -r '.expiresAt // empty' 2>/dev/null)
if [ -n "$EXPIRES_AT" ]; then
    EXPIRES_DATE=$(date -r $((EXPIRES_AT / 1000)) 2>/dev/null || echo "unknown")
    echo "   Expires: $EXPIRES_DATE"
fi

echo ""
echo "✅ Authenticated successfully!"
echo "   JWT saved to: $JWT_FILE"
echo ""
echo "   Use with: -H \"Authorization: Bearer \$(cat $JWT_FILE)\""


path: scripts/get-address.sh
content: #!/bin/bash
# Get Gigaverse wallet address

SECRETS_DIR="${HOME}/.secrets"
KEY_FILE="${SECRETS_DIR}/gigaverse-private-key.txt"
ADDR_FILE="${SECRETS_DIR}/gigaverse-address.txt"

if [ ! -f "$KEY_FILE" ]; then
    echo "❌ No wallet found. Run setup-wallet.sh first."
    exit 1
fi

ADDRESS=$(cat "$ADDR_FILE" 2>/dev/null || echo "unknown")

echo "Wallet Address: $ADDRESS"


path: scripts/mint-direct.cjs
content: #!/usr/bin/env node
/**
 * Direct EOA mint - calls contract directly from EOA
 */

const { createPublicClient, createWalletClient, http, parseEther, encodeFunctionData } = require('viem');
const { privateKeyToAccount } = require('viem/accounts');
const { abstract } = require('viem/chains');
const { readFileSync } = require('fs');
const { join } = require('path');
const { homedir } = require('os');

// Config
const CHAIN = abstract;
const RPC_URL = 'https://api.mainnet.abs.xyz';
const ACCOUNT_SYSTEM = [REDACTED_SECRET];

// ABI
const MINT_ABI = [{
  name: 'mintWithEth',
  type: 'function',
  inputs: [
    { name: '_username', type: 'string' },
    { name: 'signature', type: 'bytes' }
  ],
  outputs: [],
  stateMutability: 'payable'
}];

async function main() {
  const username = process.argv[2];
  if (!username) {
    console.error('Usage: node mint-direct.cjs <username>');
    process.exit(1);
  }

  // Load private key
  const keyPath = join(homedir(), '.secrets', 'gigaverse-private-key.txt');
  let privateKey = readFileSync(keyPath, 'utf8').trim();
  if (!privateKey.startsWith('0x')) privateKey = '0x' + privateKey;

  const account = privateKeyToAccount(privateKey);
  console.log('EOA Address:', account.address);

  // Load JWT
  const jwtPath = join(homedir(), '.secrets', 'gigaverse-jwt.txt');
  const jwt = readFileSync(jwtPath, 'utf8').trim();

  // Get mint signature for username
  console.log(`\nChecking username "${username}"...`);
  const sigResponse = await fetch(
    `https://gigaverse.io/api/indexer/usernameAvailable/${username}`,
    { headers: { Authorization: `Bearer ${jwt}` } }
  );

  if (!sigResponse.ok) {
    console.error('Failed to check username:', await sigResponse.text());
    process.exit(1);
  }

  const sigData = await sigResponse.json();
  if (!sigData.available) {
    console.error('Username not available');
    process.exit(1);
  }

  console.log('✅ Username available');
  console.log('Signature:', sigData.signature.slice(0, 20) + '...');

  // Create clients
  const publicClient = createPublicClient({
    chain: CHAIN,
    transport: http(RPC_URL),
  });

  const walletClient = createWalletClient({
    account,
    chain: CHAIN,
    transport: http(RPC_URL),
  });

  // Check balance
  const balance = await publicClient.getBalance({ address: account.address });
  console.log('\nEOA Balance:', Number(balance) / 1e18, 'ETH');

  const mintPrice = parseEther('0.005');
  if (balance < mintPrice) {
    console.error('❌ Insufficient balance for mint (need 0.005 ETH + gas)');
    process.exit(1);
  }

  // Encode function call
  const callData = encodeFunctionData({
    abi: MINT_ABI,
    functionName: 'mintWithEth',
    args: [username, sigData.signature]
  });

  console.log('\nSending transaction directly from EOA...');
  console.log('Contract:', ACCOUNT_SYSTEM);
  console.log('Function: mintWithEth');
  console.log('Username:', username);
  console.log('Value: 0.005 ETH');

  try {
    const hash = await walletClient.sendTransaction({
      to: ACCOUNT_SYSTEM,
      data: callData,
      value: mintPrice,
    });

    console.log('\n✅ Transaction sent:', hash);
    console.log('Explorer: https://abscan.org/tx/' + hash);

    console.log('\nWaiting for confirmation...');
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    
    if (receipt.status === 'success') {
      console.log('✅ Confirmed in block:', receipt.blockNumber);
      console.log('Gas used:', receipt.gasUsed.toString());
    } else {
      console.log('❌ Transaction reverted');
    }

  } catch (e) {
    console.error('\n❌ Transaction failed:', e.message);
    if (e.cause?.reason) console.error('Reason:', e.cause.reason);
    if (e.cause?.data) console.error('Data:', e.cause.data);
  }
}

main();


path: scripts/package-lock.json
content: {
  "name": "scripts",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "scripts",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "@types/node": "^25.2.2",
        "typescript": "^5.9.3",
        "viem": "^2.45.1"
      }
    },
    "node_modules/@adraffy/ens-normalize": {
      "version": "1.11.1",
      "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@noble/ciphers": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/curves": {
      "version": "1.9.1",
      "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/hashes": "1.8.0"
      },
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@noble/hashes": {
      "version": "1.8.0",
      "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^14.21.3 || >=16"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/base": {
      "version": "1.2.6",
      "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/bip32": {
      "version": "1.7.0",
      "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/curves": "~1.9.0",
        "@noble/hashes": "~1.8.0",
        "@scure/base": "~1.2.5"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@scure/bip39": {
      "version": "1.6.0",
      "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@noble/hashes": "~1.8.0",
        "@scure/base": "~1.2.5"
      },
      "funding": {
        "url": "https://paulmillr.com/funding/"
      }
    },
    "node_modules/@types/node": {
      "version": "25.2.2",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "undici-types": "~7.16.0"
      }
    },
    "node_modules/abitype": {
      "version": "1.2.3",
      "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/wevm"
      },
      "peerDependencies": {
        "typescript": ">=5.0.4",
        "zod": "^3.22.0 || ^4.0.0"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        },
        "zod": {
          "optional": true
        }
      }
    },
    "node_modules/eventemitter3": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/isows": {
      "version": "1.0.7",
      "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/wevm"
        }
      ],
      "license": "MIT",
      "peerDependencies": {
        "ws": "*"
      }
    },
    "node_modules/ox": {
      "version": "0.11.3",
      "resolved": "https://registry.npmjs.org/ox/-/ox-0.11.3.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/wevm"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "@adraffy/ens-normalize": "^1.11.0",
        "@noble/ciphers": "^1.3.0",
        "@noble/curves": "1.9.1",
        "@noble/hashes": "^1.8.0",
        "@scure/bip32": "^1.7.0",
        "@scure/bip39": "^1.6.0",
        "abitype": "^1.2.3",
        "eventemitter3": "5.0.1"
      },
      "peerDependencies": {
        "typescript": ">=5.4.0"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        }
      }
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/undici-types": {
      "version": "7.16.0",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/viem": {
      "version": "2.45.1",
      "resolved": "https://registry.npmjs.org/viem/-/viem-2.45.1.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/wevm"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "@noble/curves": "1.9.1",
        "@noble/hashes": "1.8.0",
        "@scure/bip32": "1.7.0",
        "@scure/bip39": "1.6.0",
        "abitype": "1.2.3",
        "isows": "1.0.7",
        "ox": "0.11.3",
        "ws": "8.18.3"
      },
      "peerDependencies": {
        "typescript": ">=5.0.4"
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        }
      }
    },
    "node_modules/ws": {
      "version": "8.18.3",
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10.0.0"
      },
      "peerDependencies": {
        "bufferutil": "^4.0.1",
        "utf-8-validate": ">=5.0.2"
      },
      "peerDependenciesMeta": {
        "bufferutil": {
          "optional": true
        },
        "utf-8-validate": {
          "optional": true
        }
      }
    }
  }
}


path: scripts/package.json
content: {
  "name": "scripts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "directories": {
    "lib": "lib"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/node": "^25.2.2",
    "typescript": "^5.9.3",
    "viem": "^2.45.1"
  }
}


path: scripts/purchase-juice.ts
content: #!/usr/bin/env npx ts-node
/**
 * Purchase GigaJuice subscription
 * 
 * Usage: npx ts-node purchase-juice.ts [listingId]
 *   listingId: 2 = JUICE BOX (30d, 0.01 ETH)
 *              3 = JUICE JAR (90d, 0.023 ETH)
 *              4 = JUICE TUB (180d, 0.038 ETH)
 */

import { createWalletClient, http, parseEther, encodeFunctionData, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

// Abstract Mainnet chain definition
const abstractMainnet = defineChain({
  id: 2741,
  name: 'Abstract',
  nativeCurrency: {
    decimals: 18,
    name: 'Ether',
    symbol: 'ETH',
  },
  rpcUrls: {
    default: { http: ['https://api.mainnet.abs.xyz'] },
  },
  blockExplorers: {
    default: { name: 'Abscan', url: 'https://abscan.org' },
  },
});

// GigaJuice contract
const GIGAJUICE_CONTRACT = '0xd154ab0de91094bfa8e87808f9a0f7f1b98e1ce1' as const;

// Listing prices (ETH)
const LISTING_PRICES: Record<number, string> = {
  2: '0.01',    // JUICE BOX - 30 days
  3: '0.023',   // JUICE JAR - 90 days  
  4: '0.038',   // JUICE TUB - 180 days
};

const LISTING_NAMES: Record<number, string> = {
  2: 'JUICE BOX (30 days)',
  3: 'JUICE JAR (90 days)',
  4: 'JUICE TUB (180 days)',
};

// ABI for purchaseGigaJuice function
const GIGAJUICE_ABI = [
  {
    name: 'purchaseGigaJuice',
    type: 'function',
    stateMutability: 'payable',
    inputs: [{ name: 'listingId', type: 'uint256' }],
    outputs: [],
  },
] as const;

async function purchaseJuice(listingId: number) {
  const privateKey = process.env.NOOB_PRIVATE_KEY;
  if (!privateKey) {
    throw new Error('NOOB_PRIVATE_KEY not set');
  }

  const price = LISTING_PRICES[listingId];
  const name = LISTING_NAMES[listingId];
  
  if (!price || !name) {
    throw new Error(`Invalid listingId: ${listingId}. Valid options: 2, 3, 4`);
  }

  console.log(`🧃 Purchasing ${name} for ${price} ETH...`);

  const account = privateKeyToAccount(privateKey as `0x${string}`);
  console.log(`📍 Wallet: ${account.address}`);

  const client = createWalletClient({
    account,
    chain: abstractMainnet,
    transport: http('https://api.mainnet.abs.xyz'),
  });

  // Encode the function call
  const data = encodeFunctionData({
    abi: GIGAJUICE_ABI,
    functionName: 'purchaseGigaJuice',
    args: [BigInt(listingId)],
  });

  console.log(`📝 Function data: ${data}`);
  console.log(`💰 Sending ${price} ETH to ${GIGAJUICE_CONTRACT}...`);

  try {
    const hash = await client.sendTransaction({
      to: GIGAJUICE_CONTRACT,
      data,
      value: parseEther(price),
    });

    console.log(`✅ Transaction sent!`);
    console.log(`🔗 Hash: ${hash}`);
    console.log(`🔍 Explorer: https://abscan.org/tx/${hash}`);
    
    return hash;
  } catch (error: any) {
    console.error('❌ Transaction failed:', error.message);
    if (error.cause) {
      console.error('Cause:', error.cause);
    }
    throw error;
  }
}

// Main
const listingId = parseInt(process.argv[2] || '2', 10);
purchaseJuice(listingId)
  .then(() => process.exit(0))
  .catch(() => process.exit(1));


path: scripts/setup-wallet.sh
content: #!/bin/bash
# Gigaverse Wallet Setup Script
# Usage: ./setup-wallet.sh generate | ./setup-wallet.sh import "0x..."

set -e

SECRETS_DIR="${HOME}/.secrets"
KEY_FILE="${SECRETS_DIR}/gigaverse-private-key.txt"
ADDR_FILE="${SECRETS_DIR}/gigaverse-address.txt"

# Colors
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

print_warning() {
    echo ""
    echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${RED}║  ⚠️  CRITICAL SECURITY WARNING ⚠️                           ║${NC}"
    echo -e "${RED}╠════════════════════════════════════════════════════════════╣${NC}"
    echo -e "${RED}║  Your private key controls ALL funds in this wallet.       ║${NC}"
    echo -e "${RED}║                                                            ║${NC}"
    echo -e "${RED}║  • NEVER share it with anyone                              ║${NC}"
    echo -e "${RED}║  • NEVER commit it to git                                  ║${NC}"
    echo -e "${RED}║  • NEVER paste it in chat or logs                          ║${NC}"
    echo -e "${RED}║  • BACK IT UP in a secure password manager                 ║${NC}"
    echo -e "${RED}║                                                            ║${NC}"
    echo -e "${RED}║  If compromised, ALL assets are permanently lost.          ║${NC}"
    echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
    echo ""
}

# Ensure secrets directory exists with proper permissions
mkdir -p "$SECRETS_DIR"
chmod 700 "$SECRETS_DIR"

# Check if wallet already exists
if [ -f "$KEY_FILE" ]; then
    echo -e "${YELLOW}⚠️  Wallet already exists at: $KEY_FILE${NC}"
    echo "   To create a new wallet, first backup and remove the existing key."
    echo ""
    if [ -f "$ADDR_FILE" ]; then
        echo "   Current Address: $(cat "$ADDR_FILE")"
    fi
    exit 1
fi

case "$1" in
    generate)
        echo "🔐 Generating new wallet..."
        
        # Generate random 32 bytes and convert to hex
        PRIVATE_KEY="0x$(openssl rand -hex 32)"
        
        # Derive address using cast (foundry) if available, otherwise use node
        if command -v cast &> /dev/null; then
            ADDRESS=$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)
        else
            # Fallback: use node with viem
            ADDRESS=$(node -e "
                const { privateKeyToAccount } = require('viem/accounts');
                const account = privateKeyToAccount('$PRIVATE_KEY');
                console.log(account.address);
            " 2>/dev/null || echo "")
        fi
        
        if [ -z "$ADDRESS" ]; then
            echo "❌ Failed to derive address. Install foundry (cast) or ensure viem is available."
            exit 1
        fi
        
        # Save private key
        echo "$PRIVATE_KEY" > "$KEY_FILE"
        chmod 600 "$KEY_FILE"
        
        # Save address
        echo "$ADDRESS" > "$ADDR_FILE"
        chmod 644 "$ADDR_FILE"
        
        print_warning
        
        echo -e "${GREEN}✅ Wallet created successfully!${NC}"
        echo ""
        echo "   EOA Address: $ADDRESS"
        echo "   (smart wallet will be deployed on first transaction)"
        echo ""
        echo "   Private key saved to: $KEY_FILE"
        echo ""
        echo -e "${YELLOW}📝 BACKUP YOUR KEY NOW before continuing.${NC}"
        ;;
        
    import)
        if [ -z "$2" ]; then
            echo "❌ Usage: $0 import \"0x...\""
            echo "   Provide the private key as the second argument."
            exit 1
        fi
        
        PRIVATE_KEY="$2"
        
        # Validate key format
        if [[ ! "$PRIVATE_KEY" =~ ^0x[a-fA-F0-9]{64}$ ]]; then
            echo "❌ Invalid private key format."
            echo "   Expected: 0x followed by 64 hex characters"
            exit 1
        fi
        
        echo "🔐 Importing wallet..."
        
        # Derive address
        if command -v cast &> /dev/null; then
            ADDRESS=$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)
        else
            ADDRESS=$(node -e "
                const { privateKeyToAccount } = require('viem/accounts');
                const account = privateKeyToAccount('$PRIVATE_KEY');
                console.log(account.address);
            " 2>/dev/null || echo "")
        fi
        
        if [ -z "$ADDRESS" ]; then
            echo "❌ Failed to derive address from key."
            exit 1
        fi
        
        # Save private key
        echo "$PRIVATE_KEY" > "$KEY_FILE"
        chmod 600 "$KEY_FILE"
        
        # Save address
        echo "$ADDRESS" > "$ADDR_FILE"
        chmod 644 "$ADDR_FILE"
        
        print_warning
        
        echo -e "${GREEN}✅ Wallet imported successfully!${NC}"
        echo ""
        echo "   EOA Address: $ADDRESS"
        echo "   Private key saved to: $KEY_FILE"
        ;;
        
    *)
        echo "Gigaverse Wallet Setup"
        echo ""
        echo "Usage:"
        echo "  $0 generate           Generate a new wallet"
        echo "  $0 import \"0x...\"     Import existing private key"
        echo ""
        echo "Files created:"
        echo "  ~/.secrets/gigaverse-private-key.txt  Your private key"
        echo "  ~/.secrets/gigaverse-address.txt      Your EOA address"
        ;;
esac


path: scripts/setup.sh
content: #!/bin/bash
# Gigaverse Skill Setup
# Configures wallet and play mode

set -e

SECRETS_DIR="${HOME}/.secrets"
CONFIG_DIR="${HOME}/.config/gigaverse"
CONFIG_FILE="${CONFIG_DIR}/config.json"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

echo ""
echo -e "${CYAN}╔════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║     🎮 Gigaverse Skill Setup 🎮        ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════╝${NC}"
echo ""

# Create config directory
mkdir -p "$CONFIG_DIR"
mkdir -p "$SECRETS_DIR"
chmod 700 "$SECRETS_DIR"

# Check for existing config
if [ -f "$CONFIG_FILE" ]; then
    echo -e "${YELLOW}Existing config found at: $CONFIG_FILE${NC}"
    echo ""
    read -p "Overwrite? (y/N): " OVERWRITE
    if [[ ! "$OVERWRITE" =~ ^[Yy]$ ]]; then
        echo "Setup cancelled."
        exit 0
    fi
fi

# === WALLET SETUP ===
echo ""
echo -e "${CYAN}=== Wallet Setup ===${NC}"
echo ""

KEY_FILE="${SECRETS_DIR}/gigaverse-private-key.txt"

if [ -f "$KEY_FILE" ]; then
    ADDR_FILE="${SECRETS_DIR}/gigaverse-address.txt"
    EXISTING_ADDR=$(cat "$ADDR_FILE" 2>/dev/null || echo "unknown")
    echo -e "Existing wallet found: ${GREEN}$EXISTING_ADDR${NC}"
    read -p "Use existing wallet? (Y/n): " USE_EXISTING
    
    if [[ "$USE_EXISTING" =~ ^[Nn]$ ]]; then
        echo ""
        echo "1) Generate new wallet"
        echo "2) Import private key"
        read -p "Choice (1/2): " WALLET_CHOICE
        
        if [ "$WALLET_CHOICE" = "1" ]; then
            "$SCRIPT_DIR/setup-wallet.sh" generate
        else
            read -p "Enter private key (0x...): " IMPORT_KEY
            "$SCRIPT_DIR/setup-wallet.sh" import "$IMPORT_KEY"
        fi
    fi
    WALLET_ADDRESS=$(cat "$ADDR_FILE")
else
    echo "No wallet found."
    echo ""
    echo "1) Generate new wallet"
    echo "2) Import private key"
    read -p "Choice (1/2): " WALLET_CHOICE
    
    if [ "$WALLET_CHOICE" = "1" ]; then
        "$SCRIPT_DIR/setup-wallet.sh" generate
    else
        read -p "Enter private key (0x...): " IMPORT_KEY
        "$SCRIPT_DIR/setup-wallet.sh" import "$IMPORT_KEY"
    fi
    WALLET_ADDRESS=$(cat "${SECRETS_DIR}/gigaverse-address.txt")
fi

# === MODE SELECTION ===
echo ""
echo -e "${CYAN}=== Play Mode ===${NC}"
echo ""
echo "How should the agent play Gigaverse?"
echo ""
echo -e "  ${GREEN}1) Autonomous${NC} — Agent makes all decisions"
echo "     • Generates username automatically"
echo "     • Selects faction randomly (or by preference)"
echo "     • Plays dungeons independently"
echo "     • Best for: background operation, fully automated"
echo ""
echo -e "  ${YELLOW}2) Interactive${NC} — Agent asks you at each decision"
echo "     • Asks for username preference"
echo "     • Lets you choose faction"
echo "     • Confirms before dungeon runs"
echo "     • Best for: guided play, human participation"
echo ""
read -p "Choice (1/2) [1]: " MODE_CHOICE

if [ "$MODE_CHOICE" = "2" ]; then
    MODE="interactive"
else
    MODE="autonomous"
fi

echo ""
echo -e "Selected: ${GREEN}$MODE${NC}"

# === PREFERENCES ===
echo ""
echo -e "${CYAN}=== Preferences ===${NC}"
echo ""

# Faction preference
DEFAULT_FACTION="null"
if [ "$MODE" = "autonomous" ]; then
    echo "Default faction for autonomous mode:"
    echo "  0) Random selection"
    echo "  1-N) Specific faction ID (check /factions/summary for options)"
    read -p "Choice [0]: " FACTION_CHOICE
    
    if [ -n "$FACTION_CHOICE" ] && [ "$FACTION_CHOICE" != "0" ]; then
        DEFAULT_FACTION="$FACTION_CHOICE"
    fi
fi

# Energy notifications
read -p "Notify when energy is full? (Y/n): " NOTIFY_ENERGY
if [[ "$NOTIFY_ENERGY" =~ ^[Nn]$ ]]; then
    NOTIFY_FULL="false"
else
    NOTIFY_FULL="true"
fi

# === WRITE CONFIG ===
echo ""
echo -e "${CYAN}=== Saving Configuration ===${NC}"

cat > "$CONFIG_FILE" << EOF
{
  "mode": "$MODE",
  "wallet_address": "$WALLET_ADDRESS",
  "preferences": {
    "default_faction": $DEFAULT_FACTION,
    "username_style": "random",
    "notify_on_full_energy": $NOTIFY_FULL
  },
  "installed_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
}
EOF

echo ""
echo -e "${GREEN}✅ Configuration saved to: $CONFIG_FILE${NC}"
echo ""
cat "$CONFIG_FILE"
echo ""

# === SUMMARY ===
echo -e "${CYAN}=== Setup Complete ===${NC}"
echo ""
echo "  Wallet: $WALLET_ADDRESS"
echo "  Mode: $MODE"
echo "  Config: $CONFIG_FILE"
echo ""

if [ "$MODE" = "autonomous" ]; then
    echo -e "${YELLOW}⚠️  Autonomous mode requires a funded wallet to mint Noob.${NC}"
    echo "   Send ETH to your wallet address to get started."
else
    echo "   The agent will ask you at each decision point."
fi

echo ""
echo -e "${GREEN}Ready to enter the Gigaverse! ⚔️${NC}"


path: scripts/lib/chains.ts
content: import { defineChain } from 'viem';

export const abstractMainnet = defineChain({
  id: 2741,
  name: 'Abstract',
  nativeCurrency: {
    decimals: 18,
    name: 'Ether',
    symbol: 'ETH',
  },
  rpcUrls: {
    default: { http: ['https://api.mainnet.abs.xyz'] },
  },
  blockExplorers: {
    default: { name: 'Abscan', url: 'https://abscan.org' },
  },
});


path: references/api.md
content: # Gigaverse API Reference

**Base URL:** `https://gigaverse.io/api`

**Authentication:** Bearer token in Authorization header

```bash
curl https://gigaverse.io/api/endpoint \
  -H "[REDACTED_SECRET]"
```

---

## Endpoint Map

### Authentication & Account
| Endpoint | Method | Auth | Purpose |
|----------|--------|------|---------|
| `/user/auth` | POST | No | Exchange signed message for JWT |
| `/user/me` | GET | Yes | Verify session |
| `/game/account/{address}` | GET | No | Check noob, username, canEnterGame |
| `/indexer/usernameAvailable/{name}` | GET | Yes | Check username + get mint signature |
| `/factions/summary` | GET | No | List faction options |
| `/factions/choose` | POST | Yes | Select faction |
| `/factions/player/{address}` | GET | No | Check faction selection |

### Dungeon
| Endpoint | Method | Auth | Purpose |
|----------|--------|------|---------|
| `/offchain/player/energy/{address}` | GET | No | Read current energy state |
| `/offchain/static` | GET | No | Static game data (enemies, items, etc.) |
| `/game/dungeon/today` | GET | Yes | Dungeon definitions, costs, requirements |
| `/game/dungeon/state` | GET | Yes | Current active run state |
| `/game/dungeon/action` | POST | Yes | Start run and submit moves |

### Inventory & Skills
| Endpoint | Method | Auth | Purpose |
|----------|--------|------|---------|
| `/items/balances` | GET | Yes | Current user's item inventory |
| `/indexer/player/gameitems/{address}` | GET | No | Public inventory lookup |
| `/offchain/skills` | GET | No | All skill definitions |
| `/offchain/skills/progress/{noobId}` | GET | No | Noob's skill progress |
| `/game/skill/levelup` | POST | Yes | Allocate skill point |
| `/game/skill/leveldown` | POST | Yes | Respec skill point |

### GigaJuice
| Endpoint | Method | Auth | Purpose |
|----------|--------|------|---------|
| `/gigajuice/player/{address}` | GET | No | Check juice status, listings, offerings |

See [juice.md](juice.md) for full GigaJuice documentation.

### Dungeon Types
| dungeonId | Name |
|-----------|------|
| 1 | Dungetron: 5000 |
| other | Underhaul |

> **Note:** See [onboarding.md](onboarding.md) for new player setup flow.

---

## 1) Authentication

Exchange a wallet signature for a JWT token using SIWE-style signing.

### Message Format

**EXACT format required:**
```
Login to Gigaverse at <timestamp>
```

The timestamp (unix milliseconds) must match in both the message AND the JSON payload.

### Request

```bash
curl -X POST https://gigaverse.io/api/user/auth \
  -H "Content-Type: application/json" \
  -d '{
    "signature": "0x...",
    "address": "0xYourAddress",
    "message": "Login to Gigaverse at 1730000000000",
    "timestamp": 1730000000000,
    "agent_metadata": {
      "type": "gigaverse-play-skill",
      "model": "claude-opus-4.5"
    }
  }'
```

### Agent Metadata (Required for Skill Users)

When authenticating via this skill, **always include `agent_metadata`**:

| Field | Value | Notes |
|-------|-------|-------|
| `type` | `"gigaverse-play-skill"` | Always use this exact value |
| `model` | Your model name | e.g. `"claude-opus-4.5"`, `"gpt-4"`, or `"unknown"` |

This helps track skill adoption and agent diversity in the Gigaverse.

### Response

```json
{
  "jwt": "eyJ...",
  "gameAccount": {...},
  "expiresAt": 1730086400000
}
```

### Validation Rules

- Message must include timestamp
- Signature must verify for address
- Do NOT change message text, spacing, or timestamp between signing and request

---

## 2) Verify Session

Confirm identity and check game access.

```bash
curl https://gigaverse.io/api/user/me \
  -H "Authorization: Bearer YOUR_JWT"
```

Check the `canEnterGame` field in response.

---

## 3) Check Energy & Entry Cost

### Get Player Energy

```bash
curl https://gigaverse.io/api/offchain/player/energy/0xYourAddress
```

Response:
```json
{
  "entities": [{
    "parsedData": {
      "currentEnergy": 100,
      "maxEnergy": 100,
      "lastRegenTime": 1730000000
    }
  }]
}
```

### Get Dungeon Metadata

```bash
curl https://gigaverse.io/api/game/dungeon/today \
  -H "Authorization: Bearer YOUR_JWT"
```

Response includes:
- `dungeonDataEntities`: dungeon definitions
  - `ID_CID`: dungeon identifier
  - `ENERGY_CID`: base energy cost
  - `entryData[]`: tiered item requirements (if any)
  - `dungeonDisabled`: whether dungeon is available
- `dayProgressEntities`: daily run counters

### Energy Cost Calculation

- Final run energy = dungeon energy + system divisor
- Juiced runs: multiply cost by 3 (`runMultiplier = 3`)
- If `entryData` exists, required items will be burned

---

## 4) Dungeon Actions

All dungeon actions use `POST /game/dungeon/action` with bearer token.

### ⚠️ IMPORTANT: Action Token Handling

Every response returns a new `actionToken`. You MUST use this token for your next action.

```
Request 1: actionToken: 0  →  Response: actionToken: 1
Request 2: actionToken: 1  →  Response: actionToken: 2
Request 3: actionToken: 2  →  Response: actionToken: 3
...
```

**Rules:**
- Always persist the returned `actionToken`
- Server rejects stale/mismatched tokens (~5s anti-spam window)
- If you get "Invalid action token", call `/game/dungeon/state` to resync

### Start a Run

```bash
curl -X POST https://gigaverse.io/api/game/dungeon/action \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "start_run",
    "dungeonId": 1,
    "actionToken": 0,
    "data": {
      "consumables": [],
      "isJuiced": false,
      "index": 0
    }
  }'
```

Parameters:
- `dungeonId`: which dungeon to enter
- `isJuiced`: true for 3x cost/rewards (requires player juiced status)
- `index`: entry tier (for dungeons with `entryData` tiers)
- `consumables`: items to bring into dungeon

### Combat Moves (Rock Paper Scissors)

```bash
curl -X POST https://gigaverse.io/api/game/dungeon/action \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "rock",
    "dungeonId": 1,
    "actionToken": LATEST_TOKEN,
    "data": {}
  }'
```

Actions: `rock` (⚔️ Sword), `paper` (🛡️ Shield), `scissor` (✨ Spell)

### Loot Selection

After defeating enemies, choose loot:

```bash
curl -X POST https://gigaverse.io/api/game/dungeon/action \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "loot_one",
    "dungeonId": 1,
    "actionToken": LATEST_TOKEN,
    "data": {}
  }'
```

Actions: `loot_one`, `loot_two`, `loot_three`, `loot_four`

### Other Actions

| Action | Purpose | Data Required |
|--------|---------|---------------|
| `use_item` | Use a consumable item | Yes |
| `heal_or_damage` | Heal self or deal damage | Yes |
| `flee` | Escape current encounter | No |
| `cancel_run` | Abandon dungeon run | No |

---

## Get Current Run State

```bash
curl https://gigaverse.io/api/game/dungeon/state \
  -H "Authorization: Bearer YOUR_JWT"
```

Returns current room, enemies, HP, available actions.

---

## Rate Limits

*TBD*

---

## Debug Checklist

| Error | Cause | Fix |
|-------|-------|-----|
| `401 No token provided` | Missing Authorization header | Add `Authorization: Bearer <jwt>` |
| `401 Invalid token` | Expired or malformed JWT | Re-run auth flow |
| `Invalid signature` | Message/timestamp mismatch | Ensure signed message matches JSON exactly |
| `Already in dungeon` | Active run exists | Call `/game/dungeon/state` first |
| `Dungeon does not have tier X` | Invalid `data.index` | Check dungeon's `entryData` tiers |
| `Not enough usable item slots` | Bad consumables array | Adjust consumables/entry items |
| `Invalid action token` | Stale token | Use latest token from response, or resync via `/game/dungeon/state` |
| `Player not juiced` | `isJuiced: true` but player lacks status | Set `isJuiced: false` or get juiced status |

## Response Format

Success:
```json
{
  "success": true,
  "actionToken": 5,
  "data": {...}
}
```

Error:
```json
{
  "success": false,
  "error": "Description"
}
```


path: references/dungeons.md
content: # Gigaverse Dungeon Reference

## Dungeon Types

| dungeonId | Name | Description |
|-----------|------|-------------|
| 1 | **Dungetron: 5000** | Main dungeon |
| other | **Underhaul** | Alternative dungeon |

## Room Structure

Dungeons have **4 rooms per floor**, up to **4 floors** (16 rooms total).

### Room Number → Floor Display

```javascript
function formatRoom(roomNum) {
  const floor = Math.ceil(roomNum / 4);
  const room = ((roomNum - 1) % 4) + 1;
  return `${floor}-${room}`;
}
```

| Room # | Display | Notes |
|--------|---------|-------|
| 1 | 1-1 | Floor 1, Room 1 |
| 2 | 1-2 | |
| 3 | 1-3 | |
| 4 | 1-4 | Floor 1 complete |
| 5 | 2-1 | Floor 2 starts |
| 6 | 2-2 | |
| 7 | 2-3 | |
| 8 | 2-4 | Floor 2 complete |
| 9 | 3-1 | Floor 3 starts |
| 10 | 3-2 | |
| 11 | 3-3 | |
| 12 | 3-4 | Floor 3 complete |
| 13 | 4-1 | Floor 4 starts |
| 14 | 4-2 | |
| 15 | 4-3 | |
| 16 | 4-4 | **FINAL BOSS** |

## Combat Actions

| Action | Description |
|--------|-------------|
| `rock` | ⚔️ Sword attack (high ATK) |
| `paper` | 🛡️ Shield (high DEF, adds shield) |
| `scissor` | ✨ Spell (balanced ATK/DEF) |

### Loot Selection

After defeating an enemy, select loot:

| Action | Description |
|--------|-------------|
| `loot_one` | Select 1st loot option |
| `loot_two` | Select 2nd loot option |
| `loot_three` | Select 3rd loot option |

## Starting a Run

```javascript
// Dungetron: 5000
{ action: 'start_run', dungeonId: 1, actionToken: 0, data: {} }

// Underhaul (if available)
{ action: 'start_run', dungeonId: 2, actionToken: 0, data: {} }
```

## Energy

- **Max Energy:** 240
- **Regen Rate:** 10 per hour
- **Cost per Run:** Variable (check before starting)


path: references/enemies.md
content: # Gigaverse Enemy Reference

Data source: `GET https://gigaverse.io/api/offchain/static` → `.enemies`

## Enemy List

| ID | Name | HP | Shield | Notes |
|----|------|----|----|-------|
| 1 | Red Robe | 4 | 2 | Early game |
| 2 | Black Robe | 2 | 5 | |
| 3 | Black Knight | 6 | 4 | |
| 4 | Dark Guard | 9 | 6 | |
| 5 | Paladin | 12 | 8 | |
| 6 | Fanatic | 14 | 5 | |
| 7 | Forest Robe | 15 | 5 | |
| 8 | Crimson Knight | 10 | 15 | |
| 9 | White Knight | 17 | 10 | |
| 10 | Impkin | 18 | 14 | |
| 11 | Nocturne | 22 | 15 | |
| 12 | Azure Guard | 26 | 18 | |
| 13 | Gryndor | 30 | 20 | |
| 14 | Aetherion | 30 | 20 | |
| 15 | Zyren | 30 | 20 | |
| 16 | Crusader | 48 | 30 | **Boss** - drops loot |
| 17 | Overseer | 48 | 30 | **Boss** - drops loot |
| 18 | Athena | 48 | 30 | **Boss** - drops loot |
| 19 | Archon | 48 | 30 | **Boss** - drops loot |
| 20 | Foxglove | 48 | 30 | **Boss** - drops loot |
| 21 | Summoner | 48 | 30 | **Boss** - drops loot |
| 22 | Chobo | 48 | 30 | **Boss** - drops loot |
| 23 | Dirtmaw | 8 | 5 | Underhaul |
| 24 | Grubhusk | 12 | 7 | Underhaul |
| 25 | Gobwark | 15 | 8 | Underhaul |
| 26 | Azure Aetherion | 20 | 12 | Underhaul - drops loot |
| 27 | Crimson Aetherion | 20 | 12 | Underhaul - drops loot |
| 28 | Bulwark | 24 | 12 | Underhaul |
| 29 | Ratsnout | 26 | 15 | Underhaul |
| 30 | Dighusk | 28 | 17 | Underhaul |
| 31 | Azure Gryndor | 28 | 18 | Underhaul - drops loot |
| 32 | Crimson Gryndor | 22 | 16 | Underhaul - drops loot |
| 33 | Minktrap | 26 | 20 | Underhaul |
| 34 | Ironhusk | 40 | 14 | Underhaul |
| 35 | Grimwark | 28 | 30 | Underhaul |
| 36 | Azure Zyren | 36 | 30 | Underhaul - drops loot |
| 37 | Crimson Zyren | 32 | 24 | Underhaul - drops loot |
| 38 | Skeltin | 35 | 35 | |
| 39 | Crimson Impkin | 35 | 38 | |
| 40 | Bryophytus | 40 | 45 | |
| 41 | Hand Of Bekda | 1 | 200 | **Special** |
| 42 | Gigian Dirtmaw | 6 | 2 | Gigian variant |
| 43 | Gigian Guard | 6 | 3 | |
| 44 | Gigian Knight | 8 | 2 | |
| 45 | Gigian Robe | 8 | 3 | |
| 46 | Gigian Paladin | 9 | 2 | |
| 47 | Gigian Impkin | 9 | 1 | |
| 48 | Gigian Fanatic | 12 | 4 | |
| 49 | Gigian Gobwark | 12 | 4 | |
| 50 | Gigian Skeltin | 14 | 0 | |
| 51 | Gigian Grubhusk | 14 | 3 | |
| 52 | Gigian Gryndor | 16 | 4 | |
| 53 | Gigian Nocturne | 16 | 4 | |
| 54 | Gigian Bryophytus | 19 | 4 | |
| 55 | Gigian Aetherion | 22 | 5 | |
| 56 | Gigian Zyren | 22 | 5 | |
| 57 | Garnish | 28 | 8 | |
| 58 | Char | 28 | 8 | |
| 59 | Bunyoo | 28 | 8 | |
| 60 | Dith | 28 | 8 | |
| 61 | Aiz | 28 | 8 | |
| 62 | Harambe | 28 | 8 | |

## Getting Enemy Name from State

The dungeon state returns `ENEMY_CID` which maps to enemy ID:

```javascript
// From state: entity.ENEMY_CID = 3
// Lookup: Enemy#3 → "Black Knight"

const enemyNames = {
  1: 'Red Robe',
  2: 'Black Robe', 
  3: 'Black Knight',
  // ... etc
};

const enemyName = enemyNames[state.data.entity.ENEMY_CID];
```

## Fetching Full Enemy Data

```bash
curl -s "https://gigaverse.io/api/offchain/static" | jq '.enemies'
```

Response includes:
- `ID_CID` - Enemy ID
- `NAME_CID` - Enemy name
- `MOVE_STATS_CID_array` - [rockATK, rockDEF, paperATK, paperDEF, scissorATK, scissorDEF, HP, Shield]
- `LOOT_ID_CID` - Loot drop ID (0 = no special loot)


path: references/factions.md
content: # Factions Reference

## Get Faction Data

```bash
curl https://gigaverse.io/api/factions/summary
```

Returns all factions with population stats.

---

## Faction List

| ID | Name | Notes |
|----|------|-------|
| 1 | **Crusader** | Most popular, highest activity |
| 2 | **Overseer** | |
| 3 | **Athena** | |
| 4 | **Archon** | Lowest activity |
| 5 | **Foxglove** | |
| 6 | **Summoner** | Second most active |
| 7 | **Chobo** | |

---

## Population Stats (from API)

| ID | Faction | Population | Active | Active % | Rank |
|----|---------|------------|--------|----------|------|
| 1 | Crusader | 18,313 | 3,658 | 20.06% | #1 |
| 6 | Summoner | 11,676 | 3,503 | 19.21% | #2 |
| 3 | Athena | 10,905 | 2,785 | 15.27% | #3 |
| 2 | Overseer | 6,812 | 2,312 | 12.68% | #4 |
| 7 | Chobo | 9,466 | 2,235 | 12.25% | #5 |
| 5 | Foxglove | 10,017 | 1,874 | 10.28% | #6 |
| 4 | Archon | 8,594 | 1,871 | 10.26% | #7 |

*Population data changes daily — fetch fresh data from the API.*

---

## Choosing a Faction

```bash
# Get current faction options
curl https://gigaverse.io/api/factions/summary | jq '[.entities[] | {id: .FACTION_CID, name: .NAME_CID, active: .data.activePopulation}]'

# Choose faction
curl -X POST https://gigaverse.io/api/factions/choose \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "factionId": 1,
    "expectedInputIds": [],
    "expectedInputAmounts": []
  }'

# Verify selection
curl https://gigaverse.io/api/factions/player/0xYOUR_ADDRESS
```

---

## Response Structure

```json
{
  "entities": [
    {
      "FACTION_CID": 1,
      "NAME_CID": "Crusader",
      "UINT256_CID": 18313,        // total population
      "MINT_COUNT_CID": 18553,     // total mints
      "BURN_COUNT_CID": -240,      // burns (negative)
      "data": {
        "activePopulation": 3658,
        "activePopulationPercentage": "20.06",
        "activityRank": 1,
        "dayPopulations": [...]    // daily history
      }
    }
  ]
}
```

---

## Quick Lookup (Agent Use)

```javascript
const FACTIONS = {
  1: "Crusader",
  2: "Overseer", 
  3: "Athena",
  4: "Archon",
  5: "Foxglove",
  6: "Summoner",
  7: "Chobo"
};

// Get faction name
const factionName = FACTIONS[factionId] || `Faction #${factionId}`;
```


path: references/items.md
content: # Gigaverse Items Reference

Data source: `GET https://gigaverse.io/api/offchain/gameitems` → `.entities`

## Rarity Levels

| Rarity | Name | Alert |
|--------|------|-------|
| 1 | Common | - |
| 2 | Uncommon | - |
| 3 | Rare | - |
| 4 | Epic | - |
| 5 | **Legendary** | 🔥 Notify user |
| 6 | **Relic** | 🌟 Notify user |
| 7 | **Giga** | 💎 Notify user |

**Alert threshold:** `RARITY_CID >= 5`

## Notable Rare Items (Rarity ≥ 4)

### Legendary (5)
- Boss Head/Body (Crusader, Overseer, Athena, Archon, Foxglove, Summoner, Chobo)
- Bubble Nib, Warden Lamprey, Skellfin, Mobius Dickens
- Void Romling Head/Body
- Eggspeditor 5, USDC, Naughty Gift Box

### Relic (6)
- Seeker, Anemone, Geotle
- Garnish Head/Body, Char Head/Body, Dith Head/Body, Bunyoo Head/Body
- Aiz Head/Body, Harambe Head/Body
- Giga Romling Head/Body

### Giga (7)
- (Rarest tier - check for new additions)

## Fetching Item Data

```bash
# Get all items
curl -s "https://gigaverse.io/api/offchain/gameitems" | jq '.entities'

# Get item by ID
curl -s "https://gigaverse.io/api/offchain/gameitems" | jq '.entities[] | select(.ID_CID == 103)'
```

## Item Structure

```json
{
  "ID_CID": 103,
  "NAME_CID": "Crusader Head",
  "RARITY_CID": 5,
  "GAME_ITEM_TYPE_CID": 1,
  "OFFCHAIN_TYPE_CID": "equipment"
}
```

## Building Item Lookup Map

```javascript
async function buildItemMap() {
  const res = await fetch('https://gigaverse.io/api/offchain/gameitems');
  const data = await res.json();
  
  const items = {};
  for (const item of data.entities) {
    items[item.ID_CID] = {
      name: item.NAME_CID,
      rarity: item.RARITY_CID
    };
  }
  return items;
}

// Usage
const items = await buildItemMap();
const itemName = items[103]?.name; // "Crusader Head"
const isRare = items[103]?.rarity >= 4; // true
```


path: references/juice.md
content: # GigaJuice Reference

GigaJuice is a premium subscription that enhances gameplay with significant benefits.

## API Endpoint

**Check Juice Status:** `GET /api/gigajuice/player/{address}`

No authentication required.

### Response Structure

```json
{
  "juiceData": {
    "isJuiced": false,
    "juicedSeconds": 0
  },
  "purchases": [],
  "referrals": [],
  "listings": [
    {
      "NAME_CID": "JUICE BOX",
      "ETH_MINT_PRICE_CID": 10000000000000000,
      "TIME_BETWEEN_CID": 2592000,
      "docId": "2"
    }
  ],
  "offerings": []
}
```

### Key Fields

| Field | Description |
|-------|-------------|
| `juiceData.isJuiced` | Whether player currently has active juice |
| `juiceData.juicedSeconds` | Seconds remaining on subscription |
| `listings` | Available juice packages with prices |
| `offerings` | Cosmetic items available with juice |

---

## Juice Packages

| Package | Duration | Price (ETH) | Price (Wei) | Daily Cost |
|---------|----------|-------------|-------------|------------|
| JUICE BOX | 30 days | 0.01 | 10000000000000000 | ~$0.10/day |
| JUICE CARTON | 90 days | 0.023 | 23000000000000000 | ~$0.08/day |
| JUICE TANK | 180 days | 0.038 | 38000000000000000 | ~$0.07/day |

> Longer subscriptions are more economical per day.

---

## Benefits

### Energy
- **Max Energy:** 240 → 420
- **Regen Rate:** 10/hour → 17.5/hour

### Dungeon
- **High Intensity Runs:** 3x energy cost for 3x loot
- **Extra Upgrade Option:** 4th choice (50% chance per room)
- **Daily Dungetron Runs:** 10 → 12
- **Daily Underhaul Runs:** 8 → 9

### Combat
- **Potion Slots:** 2 → 3

### Fishing
- **Daily Casts:** 10 → 20

### Movement
- **Sprint:** Hold Shift to run faster

### ROM Production
- **Material Boost:** +20% Shard & Dust production

### Abstract Ecosystem
- **Stubs Conversion:** 4x bonus at Traveling Merchant

---

## Using Juice in Runs

When starting a juiced run, set `isJuiced: true`:

```bash
curl -X POST https://gigaverse.io/api/game/dungeon/action \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "start_run",
    "dungeonId": 1,
    "actionToken": 0,
    "data": {
      "consumables": [],
      "isJuiced": true,
      "index": 0
    }
  }'
```

⚠️ **Note:** Juiced runs cost 3x energy but provide 3x rewards and the extra upgrade option chance.

---

## Purchasing Juice

### Contract Details

| Field | Value |
|-------|-------|
| **Contract** | [`0xd154ab0de91094bfa8e87808f9a0f7f1b98e1ce1`](https://abscan.org/address/0xd154ab0de91094bfa8e87808f9a0f7f1b98e1ce1) |
| **Chain** | Abstract (Chain ID: 2741) |
| **RPC** | `https://api.mainnet.abs.xyz` |
| **Function** | `purchaseGigaJuice(uint256 listingId)` |
| **Method ID** | `0x52ce66cc` |

### Listing IDs

| listingId | Package | Duration | Price (ETH) |
|-----------|---------|----------|-------------|
| 2 | JUICE BOX | 30 days | 0.01 |
| 3 | JUICE CARTON | 90 days | 0.023 |
| 4 | JUICE TANK | 180 days | 0.038 |

### Purchase via Script

Use the included purchase script:

```bash
cd skills/gigaverse/scripts
export NOOB_PRIVATE_KEY="0x..."
npx ts-node purchase-juice.ts 2  # JUICE BOX
npx ts-node purchase-juice.ts 3  # JUICE CARTON
npx ts-node purchase-juice.ts 4  # JUICE TANK
```

### Purchase via viem (Programmatic)

```typescript
import { createWalletClient, http, parseEther, encodeFunctionData, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const abstractMainnet = defineChain({
  id: 2741,
  name: 'Abstract',
  nativeCurrency: { decimals: 18, name: 'Ether', symbol: 'ETH' },
  rpcUrls: { default: { http: ['https://api.mainnet.abs.xyz'] } },
});

const GIGAJUICE_CONTRACT = '0xd154ab0de91094bfa8e87808f9a0f7f1b98e1ce1';
const GIGAJUICE_ABI = [{
  name: 'purchaseGigaJuice',
  type: 'function',
  stateMutability: 'payable',
  inputs: [{ name: 'listingId', type: 'uint256' }],
  outputs: [],
}] as const;

async function purchaseJuice(privateKey: string, listingId: number, priceEth: string) {
  const account = privateKeyToAccount(privateKey as `0x${string}`);
  const client = createWalletClient({
    account,
    chain: abstractMainnet,
    transport: http('https://api.mainnet.abs.xyz'),
  });

  const data = encodeFunctionData({
    abi: GIGAJUICE_ABI,
    functionName: 'purchaseGigaJuice',
    args: [BigInt(listingId)],
  });

  return await client.sendTransaction({
    to: GIGAJUICE_CONTRACT,
    data,
    value: parseEther(priceEth),
  });
}

// Example: Buy JUICE BOX (30 days)
await purchaseJuice(process.env.PRIVATE_KEY, 2, '0.01');
```

### Raw Transaction

For manual/low-level integration:

```
To: 0xd154ab0de91094bfa8e87808f9a0f7f1b98e1ce1
Value: 0.01 ETH (for JUICE BOX)
Data: 0x52ce66cc0000000000000000000000000000000000000000000000000000000000000002
       └─ method ─┘└─────────────────── listingId (2) ──────────────────────────────┘
```

### Verifying Purchase

After purchase, check juice status:

```bash
curl -s "https://gigaverse.io/api/gigajuice/player/{ADDRESS}" | jq '.juiceData'
```

Expected response when juiced:
```json
{
  "isJuiced": true,
  "juicedSeconds": 2592000
}
```

---

## Agent Notification Logic

When to suggest juice to players:

### Suggest If:
- Player is NOT juiced
- Player has NOT declined juice permanently (`preferences.juice_declined: false`)
- One of:
  - Energy hits max (240) — "You're capped! Juiced players get 420 max energy"
  - First death in a run — "Extra potion slot could have saved you"
  - Close call (won with <5 HP) — "Juiced runs give 50% chance for a 4th upgrade option"
  - Daily run limit reached — "Juiced players get 2 extra runs per day"

### Never Suggest If:
- `preferences.juice_declined: true` — Player explicitly declined
- UNLESS: There's an active sale/promotion (check `offerings` for limited-time items)

### Prompt Template (Interactive Mode)
```
🧃 GigaJuice Available!

You're hitting energy cap often. Juiced players get:
• 420 max energy (vs 240)
• 17.5/hour regen (vs 10)
• Extra upgrade options in dungeons
• +2 daily runs

Packages: 30d (0.01 ETH) | 90d (0.023 ETH) | 180d (0.038 ETH)

[1] Tell me more
[2] Maybe later
[3] Don't ask again
```

If player chooses [3], set `preferences.juice_declined: true`.


path: references/leveling.md
content: # Leveling Guide

Between dungeon runs, check for XP items (scrap) and level up your Noob's skills.

## Overview

- Each dungeon has its own skill tree and XP item
- Spending XP allocates points to stats (ATK, DEF, HP, Armor)
- Higher levels cost more XP (exponential scaling)
- Stats improve starting combat power for that dungeon

---

## Dungeons & XP Items

| Dungeon | Skill Name | XP Item ID | Item Name |
|---------|------------|------------|-----------|
| 1 | Dungetron 5000 | 2 | Dungeon Scrap |
| 2 | Gigus Dungeon | - | (disabled) |
| 3 | Dungetron Underhaul | 3 | (TBD) |

---

## Available Stats (Dungetron 5000)

| Stat ID | Name | Effect |
|---------|------|--------|
| 0 | Sword ATK | +1 starting Sword attack |
| 1 | Sword DEF | +1 starting Sword defense |
| 2 | Shield ATK | +1 starting Shield attack |
| 3 | Shield DEF | +1 starting Shield defense |
| 4 | Spell ATK | +1 starting Spell attack |
| 5 | Spell DEF | +1 starting Spell defense |
| 6 | Max HP | +2 starting max health |
| 7 | Max Armor | +1 starting max armor |

---

## Strategy-Based Allocation

Based on your combat strategy, prioritize different stats:

### 🗡️ Aggressive (Glass Cannon)
Priority: Sword ATK > Spell ATK > Shield ATK
- Maximize damage output
- Accept lower survivability
- Best for: Fast clears, skilled players

### 🛡️ Defensive (Tank)
Priority: Max HP > Max Armor > Shield DEF > Sword DEF
- Maximize survivability
- Accept slower fights
- Best for: New players, longer runs

### ⚖️ Balanced (Treasure Hunter)
Priority: Max HP > Sword ATK > Shield DEF > Spell ATK
- Mix of survivability and damage
- Well-rounded approach
- Best for: General use

### 🎲 Random (Chaos Agent)
Priority: Random distribution
- Unpredictable builds
- For experimentation only

---

## Pre-Run Leveling Check

**Before EVERY dungeon run**, agents should:

1. Check XP item balance:
```bash
curl https://gigaverse.io/api/items/balances \
  -H "Authorization: Bearer $JWT"
```

2. Check XP needed for next level:
```bash
curl https://gigaverse.io/api/offchain/skills
# Look at xpPerLvl array for your current level
```

3. If XP >= next level cost → Level up!

---

## Level Up Request

```bash
curl -X POST https://gigaverse.io/api/game/skill/levelup \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "skillId": 1,
    "statId": 6,
    "noobId": YOUR_NOOB_ID
  }'
```

Parameters:
- `skillId`: 1 = Dungetron 5000
- `statId`: 0-7 (see stat table above)
- `noobId`: Your Noob's ID

---

## XP Cost Table (Dungetron 5000)

| Level | XP Required | Cumulative |
|-------|-------------|------------|
| 1 | 11 | 11 |
| 2 | 14 | 25 |
| 3 | 18 | 43 |
| 4 | 22 | 65 |
| 5 | 25 | 90 |
| 6 | 29 | 119 |
| 7 | 33 | 152 |
| 8 | 37 | 189 |
| 9 | 41 | 230 |
| 10 | 45 | 275 |

*Level 10+ costs increase significantly (91+ per level)*

---

## Autonomous Behavior

In autonomous mode, agents should:

1. After each run, check scrap balance
2. If scrap >= next level cost:
   - Pick stat based on strategy config
   - Level up automatically
   - Log: "Leveled up! +1 Sword ATK (Level 3)"
3. Repeat until scrap < next level cost

### Stat Selection by Strategy

```javascript
function pickStat(strategy) {
  const priorities = {
    aggressive: [0, 4, 2, 6],  // Sword ATK, Spell ATK, Shield ATK, HP
    defensive: [6, 7, 3, 1],   // HP, Armor, Shield DEF, Sword DEF
    balanced: [6, 0, 3, 4],    // HP, Sword ATK, Shield DEF, Spell ATK
    random: () => Math.floor(Math.random() * 8)
  };
  // Rotate through priorities based on current level distribution
}
```

---

## Interactive Mode

If not autonomous, prompt user:

```
📊 LEVEL UP AVAILABLE!
XP: 25 scrap (need 18 for Level 3)

Choose stat to upgrade:
0: Sword ATK
1: Sword DEF
2: Shield ATK
3: Shield DEF
4: Spell ATK
5: Spell DEF
6: Max HP (+2)
7: Max Armor (+1)
```

---

## Check Current Progress

```bash
curl https://gigaverse.io/api/offchain/skills/progress/YOUR_NOOB_ID
```

Response:
```json
{
  "SKILL_CID": 1,
  "LEVEL_CID": 2,
  "LEVEL_CID_array": [1, null, null, null, null, null, 1]
}
```

This shows: Level 2, with +1 Sword ATK (stat 0) and +1 Max HP (stat 6).

---

## Respec (Optional)

To reallocate points (costs respec items):

```bash
curl -X POST https://gigaverse.io/api/game/skill/leveldown \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "skillId": 1,
    "statId": 0,
    "noobId": YOUR_NOOB_ID
  }'
```

Refunds 75% of XP spent.

---

## Integration with Run Loop

```
┌─────────────────┐
│ Check Energy    │
└────────┬────────┘
         │
    < 40 energy?
         │
    ┌────┴────┐
    │ YES     │ NO
    ▼         ▼
┌─────────┐ ┌─────────────┐
│ Wait    │ │ Check Scrap │
└─────────┘ └──────┬──────┘
                   │
              enough for level?
                   │
              ┌────┴────┐
              │ YES     │ NO
              ▼         ▼
         ┌─────────┐ ┌─────────────┐
         │Level Up │ │ Start Run   │
         └────┬────┘ └─────────────┘
              │
              └──► (repeat check)
```


path: references/onboarding.md
content: # Gigaverse Onboarding Reference

Before entering dungeons, new players must complete onboarding:

1. ✅ Authenticate (JWT)
2. ✅ Mint a Noob character (onchain)
3. ✅ Set a username (part of mint)
4. ✅ Choose a faction

## Wallet Setup

Generate a standard Ethereum wallet (EOA) for Gigaverse:
- Generate a fresh keypair
- Fund with ETH on Abstract chain
- Call contracts directly from your wallet

## Play Modes & Onboarding

Check `~/.config/gigaverse/config.json` for mode:

### Autonomous Mode
```
Agent handles everything automatically:
- Generates username (e.g., "Agent_7x92k")
- Selects faction (random or from config preference)
- Proceeds without asking
- Just needs funded wallet
```

### Interactive Mode
```
Agent asks at each checkpoint:
- "What username would you like for your Noob?"
- "Here are the factions: [list]. Which do you choose?"
- Waits for human response before proceeding
```

## Gate Check

**Block dungeon actions unless ALL are true:**
- `noob != null`
- `username` exists OR `usernames.length > 0`
- Faction doc exists with `FACTION_CID > 0`

If any fail → route to onboarding instead of dungeon.

---

## 1) Authenticate

```bash
curl -X POST https://gigaverse.io/api/user/auth \
  -H "Content-Type: application/json" \
  -d '{
    "signature": "0x...",
    "address": "0x...",
    "message": "Login to Gigaverse at 1730000000000",
    "timestamp": 1730000000000
  }'
```

---

## 2) Check Account State

```bash
curl https://gigaverse.io/api/game/account/0xYOUR_ADDRESS
# or
curl https://gigaverse.io/api/user/gameaccount/0xYOUR_ADDRESS
```

Check response for:
- `noob`: exists or null
- `username`: present or missing
- `canEnterGame`: boolean

---

## 3) Reserve Username & Get Mint Signature

⚠️ **CRITICAL: Username must be lowercase!**

The API signs `username.toLowerCase().trim()`. You MUST use the same normalized username for both the API call and the contract call.

```bash
# Always lowercase!
USERNAME="myusername"  # not "MyUserName"

curl "https://gigaverse.io/api/indexer/usernameAvailable/$USERNAME" \
  -H "Authorization: Bearer YOUR_JWT"
```

Response:
```json
{
  "available": true,
  "signature": "0x..."
}
```

The `signature` is required for the onchain mint call. **Use the exact same lowercase username in the contract call.**

---

## 4) Mint Noob (Onchain)

Call the **AccountSystem** contract at [REDACTED_SECRET].

⚠️ **Use the SAME lowercase username from step 3!**

### Option A: Mint with ETH (0.005 ETH)
```solidity
mintWithEth(string _username, bytes signature)
```

```javascript
// Example with viem
await walletClient.writeContract({
  address: [REDACTED_SECRET],
  abi: mintAbi,
  functionName: 'mintWithEth',
  args: ['myusername', signature],  // lowercase!
  value: parseEther('0.005')
});
```

Payable — requires 0.005 ETH mint fee.

### Option B: Mint with Referral
```solidity
mintWithEthReferral(string _username, address _referrer, bytes signature)
```
Payable — referrer gets bonus.

**Signature source:** The `signature` from step 3 (`/indexer/usernameAvailable`).

---

## 5) Verify Noob & Username

After mint transaction confirms:

```bash
curl https://gigaverse.io/api/account/0xYOUR_ADDRESS
```

Check:
- `noob`: should now exist
- `usernames[]`: should contain your name

---

## 6) Get Faction Options

```bash
curl https://gigaverse.io/api/factions/summary
```

Response includes `entities[]` with:
- Faction IDs
- Names/descriptions
- Expected input cost arrays

---

## 7) Choose Faction

```bash
curl -X POST https://gigaverse.io/api/factions/choose \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "factionId": 1,
    "expectedInputIds": [],
    "expectedInputAmounts": []
  }'
```

For first-time choice, expected arrays are typically empty.

---

## 8) Verify Faction Selection

```bash
curl https://gigaverse.io/api/factions/player/0xYOUR_ADDRESS
```

Confirm:
- Faction record exists
- `FACTION_CID > 0`

---

## 9) Ready for Dungeons!

Only after all gates pass:

```bash
# Check energy
curl https://gigaverse.io/api/offchain/player/energy/0xYOUR_ADDRESS

# Get dungeon info
curl https://gigaverse.io/api/game/dungeon/today \
  -H "Authorization: Bearer YOUR_JWT"

# Start run
curl -X POST https://gigaverse.io/api/game/dungeon/action \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{"action":"start_run","dungeonId":1,"actionToken":0,"data":{...}}'
```

---

## Quick Gate Check Script

```bash
#!/bin/bash
ADDRESS="0xYOUR_ADDRESS"
JWT="YOUR_JWT"

# Check account
ACCOUNT=$(curl -s "https://gigaverse.io/api/game/account/$ADDRESS")

NOOB=$(echo "$ACCOUNT" | jq -r '.noob // empty')
USERNAME=$(echo "$ACCOUNT" | jq -r '.username // .usernames[0] // empty')

# Check faction
FACTION=$(curl -s "https://gigaverse.io/api/factions/player/$ADDRESS")
FACTION_CID=$(echo "$FACTION" | jq -r '.FACTION_CID // 0')

echo "Noob: ${NOOB:-NOT SET}"
echo "Username: ${USERNAME:-NOT SET}"
echo "Faction CID: $FACTION_CID"

if [ -n "$NOOB" ] && [ -n "$USERNAME" ] && [ "$FACTION_CID" -gt 0 ]; then
    echo "✅ Ready for dungeons!"
else
    echo "❌ Onboarding incomplete"
fi
```

---

## Onboarding Flow Summary

```
┌─────────────────────────────────────────────────────────┐
│  1. Authenticate (JWT)                                  │
│       ↓                                                 │
│  2. Check account state                                 │
│       ↓                                                 │
│  ┌─ No noob? ──────────────────────────────────────┐   │
│  │  3. Reserve username → get signature            │   │
│  │  4. Mint noob onchain (contract call)           │   │
│  │  5. Verify noob exists                          │   │
│  └─────────────────────────────────────────────────┘   │
│       ↓                                                 │
│  ┌─ No faction? ───────────────────────────────────┐   │
│  │  6. Get faction options                         │   │
│  │  7. Choose faction                              │   │
│  │  8. Verify faction selection                    │   │
│  └─────────────────────────────────────────────────┘   │
│       ↓                                                 │
│  9. Ready for dungeons! 🎮                              │
└─────────────────────────────────────────────────────────┘
```


path: references/run-tracking.md
content: # Dungeon Run Tracking

Track loot and progress across dungeon runs.

---

## Two Types of "Loot"

**1. Boons (in-run upgrades)** — Chosen after each room:
- UpgradeRock, UpgradePaper, UpgradeScissor (stat boosts)
- Heal (restore HP)
- These are temporary for the current run only

**2. Items (permanent rewards)** — Collected during runs:
- Scrap, Bolts, Pipes, etc.
- Added to inventory automatically
- Used for leveling, crafting, etc.

---

## Reading Loot Options

After defeating an enemy, check `lootOptions`:

```bash
curl https://gigaverse.io/api/game/dungeon/state \
  -H "Authorization: Bearer $JWT" | jq '.data.run.lootOptions'
```

Response:
```json
[
  {"boonTypeString": "UpgradeRock", "RARITY_CID": 1},
  {"boonTypeString": "Heal", "RARITY_CID": 0, "selectedVal1": 8},
  {"boonTypeString": "UpgradePaper", "RARITY_CID": 3}
]
```

### Displaying Loot Options

Format for humans:
```
Room 2 cleared! Choose loot:
1. ⚔️ Upgrade Sword (Uncommon)
2. 💚 Heal +8 HP (Common)
3. 🛡️ Upgrade Shield (Rare!)
```

Rarity mapping:
| RARITY_CID | Name | Emoji |
|------------|------|-------|
| 0 | Common | (none) |
| 1 | Uncommon | (none) |
| 2 | Rare | ! |
| 3 | Epic | !! |
| 4 | Legendary | 🔥 |
| 5+ | Mythic+ | 💎 |

---

## Tracking Inventory Changes

**Check inventory BEFORE and AFTER each run** to see what items were collected:

```bash
# Before run
curl https://gigaverse.io/api/items/balances \
  -H "Authorization: Bearer $JWT" > /tmp/inv-before.json

# ... complete run ...

# After run
curl https://gigaverse.io/api/items/balances \
  -H "Authorization: Bearer $JWT" > /tmp/inv-after.json

# Diff to see new items
```

### Item ID Reference

Common drop items:
| ID | Name | Rarity |
|----|------|--------|
| 2 | Dungeon Scrap | Common |
| 4 | Bolt | Uncommon |
| 5 | Steel Pipe | Uncommon |
| 22 | Fiber | Common |

See `references/items.md` for full list.

---

## Run Summary Format

After each run, display:

```
📊 RUN COMPLETE
━━━━━━━━━━━━━━━━━━━━━━
Result: ✅ Victory
Dungeon: Dungetron: 5000
Rooms: 4/4 (16 total)
Final HP: 8/12

Boons Collected:
- ⚔️ +2 Sword ATK (Epic)
- 💚 Heal +8 HP
- 🛡️ +1 Shield DEF

Items Collected:
- Dungeon Scrap x3
- Bolt x1
━━━━━━━━━━━━━━━━━━━━━━
```

### For Defeats:

```
💀 RUN ENDED
━━━━━━━━━━━━━━━━━━━━━━
Result: ❌ Died in Room 4 (Floor 1-4)
Enemy: Black Knight (HP: 1 remaining)
Final HP: 0/12

Boons Collected:
- ⚔️ +1 Sword ATK
- 🛡️ +2 Shield ATK (Rare)

Items Collected:
- Dungeon Scrap x2
━━━━━━━━━━━━━━━━━━━━━━
```

---

## Daily Tally Format

Keep a running tally in memory or a daily file:

```json
{
  "date": "2026-02-06",
  "runs": 3,
  "runResults": [
    { "result": "victory", "roomsCleared": 16, "loot": [{"id": 2, "name": "Dungeon Scrap", "qty": 5}] },
    { "result": "defeat", "roomsCleared": 7, "loot": [{"id": 22, "name": "Fiber", "qty": 2}] },
    { "result": "victory", "roomsCleared": 16, "loot": [{"id": 103, "name": "Crusader Head", "qty": 1, "rarity": 5, "RARE": true}] }
  ],
  "totalLoot": {
    "2": { "name": "Dungeon Scrap", "qty": 5 },
    "22": { "name": "Fiber", "qty": 2 },
    "103": { "name": "Crusader Head", "qty": 1, "rarity": 5 }
  },
  "rareFinds": [
    { "id": 103, "name": "Crusader Head", "rarity": 5, "run": 3 }
  ]
}
```

## Tracking Loot During Run

After each room/loot selection, the response includes `GAME_ITEM_ID_CID_array`:

```javascript
// From dungeon state or action response:
const itemIds = state.data.entity.GAME_ITEM_ID_CID_array;

// Map to names using item lookup
for (const id of itemIds) {
  const item = itemMap[id];
  if (item) {
    tally.totalLoot[id] = tally.totalLoot[id] || { name: item.name, qty: 0, rarity: item.rarity };
    tally.totalLoot[id].qty++;
    
    // Check for rare
    if (item.rarity >= 5) {
      notifyUser(`🔥 RARE LOOT: ${item.name} (Rarity ${item.rarity})`);
      tally.rareFinds.push({ id, name: item.name, rarity: item.rarity, run: tally.runs });
    }
  }
}
```

## Rare Item Alert

**Threshold:** `RARITY_CID >= 5`

When a rare item is found, immediately notify the user:

```
🔥 RARE LOOT FOUND!
Item: Crusader Head
Rarity: 5 (Legendary)
Run: #3 of today
Room: 4-4 (Final Boss)
```

## End of Run Summary

After each run completes (victory or defeat):

```
📊 RUN #3 COMPLETE

Result: ✅ Victory
Dungeon: Dungetron: 5000
Rooms Cleared: 16 (4-4)
Final HP: 8/12

Loot Collected:
- Dungeon Scrap x3
- Fiber x2
- 🔥 Crusader Head (Mythic)

Daily Total: 3 runs, 2 victories
```

## Daily Summary

At end of day or on request:

```
📊 DAILY DUNGEON SUMMARY - 2026-02-06

Runs: 5
Victories: 3 (60%)
Defeats: 2

Total Loot:
- Dungeon Scrap x12
- Fiber x8
- Wood x5
- 🔥 Crusader Head x1 (Mythic)
- 🔥 Aetherion Body x1 (Legendary)

Rare Finds: 2 items
Energy Used: ~200
Energy Remaining: 40/240
```

## State File Location

Store daily tracking in:
```
~/.config/gigaverse/runs/YYYY-MM-DD.json
```

Or in agent memory:
```
memory/gigaverse-YYYY-MM-DD.json
```


path: references/skills-inventory.md
content: # Skills & Inventory Reference

## Overview

Noobs can level up skills by spending XP items. Each skill has multiple stats to allocate points into.

---

## Inventory

### Authenticated (Current User)

```bash
curl https://gigaverse.io/api/items/balances \
  -H "Authorization: Bearer $JWT"
```

Returns `entities[]` with item balances.

### Public Lookup (Any Wallet)

```bash
curl https://gigaverse.io/api/indexer/player/gameitems/0xADDRESS
```

No JWT required. Returns onchain balances.

---

## Skills

### Get Skill Definitions

```bash
curl https://gigaverse.io/api/offchain/skills
```

No JWT required. Returns all skills with:
- `docId` — Skill ID
- `GAME_ITEM_ID_CID` — XP item required for this skill
- `LEVEL_CID` — Max level
- `stats[]` — Stat definitions for allocation

### Get Noob's Skill Progress

```bash
curl https://gigaverse.io/api/offchain/skills/progress/NOOB_ID
```

No JWT required. Returns:
- `SKILL_CID` — Skill ID
- `LEVEL_CID` — Current skill level
- `LEVEL_CID_array` — Allocated stat levels per stat

---

## Level Up (Allocate Point)

```bash
curl -X POST https://gigaverse.io/api/game/skill/levelup \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "skillId": 1,
    "statId": 0,
    "noobId": 81087
  }'
```

**What happens:**
- Burns required XP item for next level
- Increments `LEVEL_CID`
- Increments `LEVEL_CID_array[statId]`

---

## Level Down (Respec)

```bash
curl -X POST https://gigaverse.io/api/game/skill/leveldown \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "skillId": 1,
    "statId": 0,
    "noobId": 81087
  }'
```

**Requirements:**
- Skill must allow respec (`allowLevelDown`)
- Burns respec input item (`INPUT_ID_CID` / `INPUT_AMOUNT_CID`)
- Spends respec energy cost
- Refunds portion of XP based on skill config

---

## Preflight Checklist

Before leveling up/down:

1. **Get Noob ID:**
   ```bash
   curl https://gigaverse.io/api/game/account/0xADDRESS
   ```

2. **Get skill definitions:**
   ```bash
   curl https://gigaverse.io/api/offchain/skills
   ```

3. **Get current progress:**
   ```bash
   curl https://gigaverse.io/api/offchain/skills/progress/NOOB_ID
   ```

4. **Get inventory:**
   ```bash
   curl https://gigaverse.io/api/items/balances \
     -H "Authorization: Bearer $JWT"
   ```

5. **Verify XP/input balance** for intended operation

---

## Common Errors

| Error | Cause |
|-------|-------|
| `Noob not found for user` | Wallet hasn't minted a noob |
| `Noob does not belong to player` | noobId doesn't match JWT wallet |
| `Skill doesn't exist` | Invalid skillId |
| `Skill at max level` | Already maxed |
| `Skill at min level` | Can't respec below 0 |
| `Stat index out of bounds` | Invalid statId |
| Insufficient balance | Not enough XP items or respec items |

---

## Endpoints Summary

| Endpoint | Auth | Description |
|----------|------|-------------|
| `GET /items/balances` | JWT | Current user's inventory |
| `GET /indexer/player/gameitems/{address}` | None | Public inventory lookup |
| `GET /offchain/skills` | None | All skill definitions |
| `GET /offchain/skills/progress/{noobId}` | None | Noob's skill progress |
| `POST /game/skill/levelup` | JWT | Allocate skill point |
| `POST /game/skill/leveldown` | JWT | Respec skill point |


Security scan verdicts — ClawScan: clean | static analysis:  | VirusTotal: clean | SkillSpector: clean (LOW)