# LLM_README.md - @kradle/challenges API Reference

This document provides exhaustive API documentation for AI agents using the `@kradle/challenges` package. This is the complete reference for creating Minecraft datapack-based challenges.

## Table of Contents

1. [Overview](#overview)
2. [Installation](#installation)
3. [Core Concepts](#core-concepts)
4. [createChallenge API](#createchallenge-api)
5. [Variables](#variables)
6. [Events](#events)
7. [Actions](#actions)
8. [Utilities](#utilities)
9. [Sandstone Integration](#sandstone-integration)
10. [Complete Examples](#complete-examples)
11. [Common Patterns](#common-patterns)

---

## Overview

`@kradle/challenges` is a TypeScript framework for creating Minecraft challenges that compile to datapacks. It provides:

- **Variable System**: Track per-player and global game state with automatic tick updates
- **Event System**: Lifecycle hooks and custom event triggers based on scores/advancements
- **Actions Library**: Pre-built game operations (give items, teleport, announce, etc.)
- **Role Management**: Assign players to teams with role-specific win conditions
- **Sandstone Integration**: Full access to Sandstone's Minecraft command generation

**Key Principle**: Challenges are defined declaratively. You specify variables, events, and conditions - the framework generates the datapack.

---

## Installation

```bash
npm install @kradle/challenges sandstone@0.14.0-alpha.13
```

**Requirements:**
- Node.js >= 22.18.0
- Sandstone 0.14.0-alpha.13 (peer dependency)

---

## Core Concepts

### Ticks

Minecraft runs at 20 ticks per second. Time values in this API are in ticks:
- 1 second = 20 ticks
- 1 minute = 1200 ticks (60 * 20)
- 5 minutes = 6000 ticks

### Namespace & Item IDs

**IMPORTANT:** Most Actions require the full `minecraft:` prefix for items, blocks, and entities:
- ✅ Correct: `item: "minecraft:diamond_sword"`
- ❌ Wrong: `item: "diamond_sword"`

The only exceptions are when the API explicitly accepts an item without the namespace (check each Action's documentation).

**Examples:**
- Items: `"minecraft:diamond"`, `"minecraft:iron_sword"`, `"minecraft:cooked_beef"`
- Blocks: `"minecraft:stone"`, `"minecraft:diamond_block"`
- Entities: `"minecraft:zombie"`, `"minecraft:pig"`, `"minecraft:creeper"`

### Scores

All variables are backed by Minecraft scoreboards. The `Score` type from Sandstone represents a scoreboard value. Scores support comparison methods:
- `score.equalTo(value)` / `score.equalTo(otherScore)`
- `score.greaterThan(value)`
- `score.greaterOrEqualThan(value)`
- `score.lowerThan(value)`
- `score.lowerOrEqualThan(value)`

### Roles

Roles define player groups (e.g., teams). Each role can have different win conditions. All players assigned to a role share that role's win condition.

---

## createChallenge API

### Required Imports

```typescript
import { createChallenge, Actions, forEveryPlayer } from "@kradle/challenges";
import { _, execute, Selector, rel, abs } from "sandstone";
import type { Score } from "sandstone";
```

### Signature

```typescript
function createChallenge<
  ROLES extends readonly string[],
  VARIABLES extends Record<string, _InputVariableType>
>(config: _BaseConfig<ROLES, VARIABLES>): ChallengeBuilder
```

### Configuration Object

```typescript
interface _BaseConfig<ROLES, VARIABLES> {
  // Required: Challenge name (used for datapack namespace)
  name: string;

  // Required: Output path for generated datapack
  kradle_challenge_path: string;

  // Required: Player roles as readonly tuple
  // Example: ["attacker", "defender"] as const
  roles: ROLES;

  // Required: Custom variable definitions
  custom_variables: VARIABLES;

  // Optional: Game duration in ticks (default: 6000 = 5 minutes)
  GAME_DURATION?: number;
}
```

### Builder Methods

The builder uses a fluent API. Methods must be called in order:

```typescript
createChallenge(config)
  .events(eventCallback)           // Define lifecycle events
  .custom_events(customEventCallback)  // Define triggered events
  .end_condition(endConditionCallback) // Define game end condition
  .win_conditions(winConditionsCallback) // Define win conditions (triggers build)
```

#### `.events(callback)`

```typescript
.events((variables: Variables, roles: Roles) => {
  return {
    start_challenge?: () => void;    // Runs once when game starts
    init_participants?: () => void;  // Runs once per player after start
    on_tick?: () => void;            // Runs every tick for each player
    end_challenge?: () => void;      // Runs once when game ends
  };
})
```

#### `.custom_events(callback)`

```typescript
.custom_events((variables: Variables, roles: Roles) => {
  return Array<ScoreEvent | AdvancementEvent>;
})
```

**Score Event:**
```typescript
{
  score: Score;           // Variable to watch
  target: number;         // Threshold value
  mode: "fire_once" | "repeatable";
  actions: () => void;    // Actions to execute
}
```

**Advancement Event:**
```typescript
{
  criteria: Array<{ trigger: string; conditions?: object }>;
  mode: "fire_once" | "repeatable";
  actions: () => void;
}
```

#### `.end_condition(callback)`

```typescript
.end_condition((variables: Variables, roles: Roles) => {
  return Condition;  // Returns a Sandstone condition
})
```

#### `.win_conditions(callback)`

```typescript
.win_conditions((variables: Variables, roles: Roles) => {
  return {
    [roleName: string]: Condition;  // One condition per role
  };
})
```

---

## Variables

### Built-in Variables

These are always available in every challenge:

| Variable | Type | Description |
|----------|------|-------------|
| `death_count` | individual | Player's death count (Minecraft `deathCount` objective) |
| `has_never_died` | individual | 1 if player has never died, 0 otherwise |
| `alive_players` | global | Count of living participants |
| `main_score` | individual | Primary score shown on sidebar |
| `game_timer` | global | Ticks since game start (increments each tick) |
| `game_state` | global | 0=CREATED, 1=OFF, 2=ON |
| `player_count` | global | Total participant count |
| `player_number` | individual | Unique player ID (1 to N) |

### Custom Variable Definition

```typescript
custom_variables: {
  variable_name: {
    type: "individual" | "global";
    objective_type?: string;  // Minecraft objective criterion
    hidden?: boolean;         // Hide from scoreboard (global only)
    default?: number;         // Initial value
    updater?: UpdaterFunction;
  }
}
```

### Variable Types

#### Individual Objective-Based

Automatically tracks Minecraft statistics:

```typescript
pigs_killed: {
  type: "individual",
  objective_type: "minecraft.killed:minecraft.pig",
  default: 0,
}
```

Common objective types:
- `"minecraft.killed:minecraft.<entity>"` - Entities killed
- `"minecraft.killed_by:minecraft.<entity>"` - Killed by entity
- `"minecraft.picked_up:minecraft.<item>"` - Items picked up
- `"minecraft.mined:minecraft.<block>"` - Blocks mined
- `"minecraft.used:minecraft.<item>"` - Items used
- `"minecraft.crafted:minecraft.<item>"` - Items crafted
- `"playerKillCount"` - PvP kills
- `"deathCount"` - Deaths
- `"dummy"` - Manual/computed value

You can find objectives definition on the [Scoreboard](https://minecraft.fandom.com/wiki/Scoreboard) wiki pagem as well as the [Statistics](https://minecraft.fandom.com/wiki/Statistics) page for more in-depth explanation.

#### Individual Dummy

Computed per-player values:

```typescript
current_y_position: {
  type: "individual",
  objective_type: "dummy",
  updater: (value) => {
    value.set(Actions.getCurrentPlayerPosition().y)
  },
}
```

#### Global Variables

Shared across all players:

```typescript
max_score_global: {
  type: "global",
  hidden: false,
  default: 0,
  updater: (value, { main_score }) => {
    value.set(0);
    forEveryPlayer(() => {
      _.if(main_score.greaterThan(value), () => {
        value.set(main_score);
      });
    });
  },
}
```

### Updater Function Signature

```typescript
type UpdaterFunction = (
  value: Score,                    // Current variable's score
  variables: Record<string, Score> // All variables including built-ins
) => void;
```

Updaters run every tick. They should be idempotent. They are not necessary if the variable is individual and tracks a Minecraft objective.

### Score Methods

All variables are `Score` objects with these methods:

```typescript
// Set value
score.set(number | Score);

// Arithmetic
score.add(number | Score);
score.remove(number | Score);

// Comparison (return Condition)
score.equalTo(number | Score);
score.greaterThan(number | Score);
score.greaterOrEqualThan(number | Score);
score.lowerThan(number | Score);
score.lowerOrEqualThan(number | Score);
```

---

## Events

### Lifecycle Events

All lifecycle events run globally (not per-player).

#### `start_challenge`

Runs once when the challenge starts. Use for global setup.

```typescript
start_challenge: () => {
  Actions.setTime({ time: "day" });
  Actions.gamerule({ rule: "doDaylightCycle", value: false });
  Actions.announce({ message: "Game starting!" });
}
```

#### `init_participants`

Runs 1 second after start_challenge. Use for player setup. It still runs globally.

```typescript
init_participants: () => {
  Actions.give({ target: "all", item: "minecraft:diamond_sword", count: 1 });
  Actions.setAttribute({ target: "all", attribute_: "generic.max_health", value: 40 });
  Actions.teleport({ target: "all", x: 0, y: 100, z: 0, absolute: true });
}
```

#### `on_tick`

Runs every tick. Use sparingly for performance.

```typescript
on_tick: () => {
  // Check something every tick
}
```

#### `end_challenge`

Runs once when the game ends. Use for cleanup and announcements.

```typescript
end_challenge: () => {
  Actions.announce({ message: "Game over!" });
}
```

### Custom Events

Custom events allow you to trigger actions based on score thresholds or Minecraft advancement criteria.

#### Score-Based Events

Score events watch a variable and trigger when it reaches a specified target value. They are evaluated every tick.

**Parameters:**
- `score` (required): The `Score` variable to watch
- `target` (optional): The threshold value. If omitted, triggers on any score change
- `mode` (required): `"fire_once"` or `"repeatable"`
- `actions` (required): Function containing actions to execute

**How it works:**
1. Every tick, the system compares the current score to the target
2. For `"fire_once"`: triggers once when `score == target` (tracks previous value to detect the 1st time threshold is met)
3. For `"repeatable"`: triggers every tick while `score == target`
4. For individual variables, events fire per-player; for global variables, events fire once globally

```typescript
{
  score: variables.diamonds,
  target: 10,
  mode: "fire_once",
  actions: () => {
    Actions.announce({ message: "10 diamonds collected!" });
  },
}
```

**Without target (triggers on any change):**
```typescript
{
  score: variables.death_count,
  mode: "fire_once",  // Triggers once when death_count changes away from initial value
  actions: () => {
    Actions.announce({ message: "First death!" });
  },
}
```

**Modes:**
- `"fire_once"`: Triggers once when threshold is reached (per player for individual variables). Uses previous tick comparison to detect when the score crosses the target.
- `"repeatable"`: Triggers every tick while `score >= target`. Useful for continuous effects.

#### Advancement-Based Events

Advancement events trigger when a Minecraft advancement criterion is met. Internally, an advancement is created that grants when the criterion triggers, which then fires the event. The `criteria` array follows the [Minecraft Advancement JSON format](https://minecraft.fandom.com/wiki/Advancement/JSON_format).

Advancement-based events always fire per-player.

**Parameters:**
- `criteria` (required): Array of advancement trigger objects with optional conditions
- `mode` (required): `"fire_once"` or `"repeatable"`
- `actions` (required): Function containing actions to execute

**How it works:**
1. An advancement is generated with your specified criteria
2. When Minecraft grants the advancement (criterion met), the event fires
3. For `"repeatable"`: the advancement is automatically revoked so it can trigger again
4. For `"fire_once"`: the advancement stays granted, preventing re-triggering

**Simple example - Track when a player hits another player:**
```typescript
{
  criteria: [
    {
      trigger: "minecraft:player_hurt_entity",
      conditions: {
        entity: { type: "minecraft:player" }
      }
    }
  ],
  mode: "repeatable",
  actions: () => {
    Actions.increment({ variable: variables.pvp_hits });
    Actions.announce({ message: "PvP hit!" });
  },
}
```

**Multiple triggers example:**
```typescript
{
  criteria: [
    { trigger: "minecraft:player_hurt_entity" },
    { trigger: "minecraft:entity_hurt_player" },
  ],
  mode: "repeatable",
  actions: () => {
    Actions.increment({ variable: variables.combat_actions });
  },
}
```

Common triggers:
- `"minecraft:player_hurt_entity"` - Player attacks entity
- `"minecraft:entity_hurt_player"` - Entity attacks player
- `"minecraft:player_killed_entity"` - Player kills entity
- `"minecraft:consume_item"` - Item consumed
- `"minecraft:inventory_changed"` - Inventory changes
- `"minecraft:location"` - Player at location
- `"minecraft:enter_block"` - Player enters block

See the [Minecraft Wiki](https://minecraft.fandom.com/wiki/Advancement/JSON_format#List_of_triggers) for the full list of triggers and their conditions.

---

## Actions

Actions are higher-level functions that wrap common Minecraft operations. They provide:
- Automatic target mapping (`"all"`, `"self"`, team names → proper selectors)
- Integration with Kradle's interface (e.g., `Actions.announce` messages appear in Kradle)
- Consistent API for common operations

For advanced use cases not covered by Actions, you can fall back to Sandstone's lower-level functions directly (`give`, `tellraw`, `effect`, `kill`, `execute`, etc.). See [Sandstone Integration](#sandstone-integration).

All actions are called via the `Actions` object:

```typescript
import { Actions } from "@kradle/challenges";
```

### Target Parameter

Many actions accept a `target` parameter of type `TargetNames`, which can be:
- `"all"` - Targets all participants (maps to `@a[tag=kradle_participant]`)
- `"self"` - Targets the current player (maps to `@s`)
- Any `Selector` instance - Custom selector for fine-grained targeting (e.g., `Selector("@a", { team: "red" })`)

### Communication

#### `Actions.announce(params)`

Broadcast message to all players with KRADLE tag.

```typescript
Actions.announce({
  message: JSONTextComponent;  // Message (string or formatted object)
});

// Simple string:
Actions.announce({ message: "Game starting!" });

// Formatted JSONTextComponent:
Actions.announce({
  message: [
    { text: "Player ", color: "white" },
    { selector: "@s", color: "gold", bold: true },
    { text: " won the game!", color: "green" }
  ]
});
```

#### `Actions.tellraw(params)`

Send formatted message to specific target. **Note:** These messages are only visible to players in-game and will not appear in Kradle's interface. Use `Actions.announce` for messages that should be visible in Kradle.

```typescript
Actions.tellraw({
  target: TargetNames;       // "all", "self", or any selector
  message: JSONTextComponent; // Message (string or formatted object)
});

// Examples:
Actions.tellraw({
  target: "all",
  message: ["Hello, ", { text: "world!", color: "gold", bold: true }]
});
Actions.tellraw({
  target: "self",
  message: "You won!"
});
Actions.tellraw({
  target: "self",
  message: { text: "Critical hit!", color: "red", bold: true }
});
```

### Items & Inventory

#### `Actions.give(params)`

Give items to a target.

```typescript
Actions.give({
  target: TargetNames;  // "all", "self", or any selector
  item: string;         // Item ID (with "minecraft:" prefix)
  count?: number;       // Amount (default: 1)
});

// Examples:
Actions.give({ target: "self", item: "minecraft:diamond_sword", count: 1 });
Actions.give({ target: "all", item: "minecraft:diamond", count: 10 });
Actions.give({ target: Selector("@a", { team: "red" }), item: "minecraft:iron_sword", count: 1 });
```

**Note:** The `target` parameter can be:
- `"all"` - All participants
- `"self"` - Current player (`@s`)
- Any `Selector` instance for custom targeting

#### `Actions.giveLoot(params)`

Give random items from weighted loot table.

```typescript
Actions.giveLoot({
  target: TargetNames;  // "all", "self", or any selector
  items: [{ name: ITEMS; count: number; weight: number }];  // Weighted item list
});

// Example:
Actions.giveLoot({
  target: "self",
  items: [
    { name: "minecraft:diamond", count: 5, weight: 1 },
    { name: "minecraft:iron_ingot", count: 10, weight: 3 },
    { name: "minecraft:gold_ingot", count: 7, weight: 2 }
  ]
});
```

**Note:** This creates a weighted loot table. Items with higher weights are more likely to be selected.

#### `Actions.clear(params)`

Clear all items from a target's inventory.

```typescript
Actions.clear({
  target: TargetNames;  // "all", "self", or any selector
});

// Examples:
Actions.clear({ target: "self" });  // Clear current player's inventory
Actions.clear({ target: "all" });   // Clear all participants' inventories
```

#### `Actions.countItems(params)`

Count the number of a specific item in a target's inventory. Creates and returns a temporary variable with the count. This is the prefered way of counting items.

```typescript
Actions.countItems({
  target: TargetNames;  // The target to count items for
  item: ITEMS;          // The item to count
}): Score;              // Returns a new variable containing the count

// Example - Use directly in conditions:
_.if(Actions.countItems({ target: "self", item: "minecraft:diamond" }).greaterThan(5), () => {
  Actions.announce({ message: "You have more than 5 diamonds!" });
});

// Example - Store in a variable for later use:
const diamondCount = Actions.countItems({ target: "self", item: "minecraft:diamond" });
_.if(diamondCount.greaterThan(10), () => {
  Actions.announce({ message: "You have more than 10 diamonds!" });
});

// Example - Set a custom variable from the count:
const count = Actions.countItems({ target: "self", item: "minecraft:diamond" });
variables.my_diamond_count.set(count);
```

**Note:** This action creates a temporary variable internally using `Variable()` and uses `execute.store.result.score` with `clear` command (count 0) to count items without removing them from the inventory.

#### `Actions.getCurrentPlayerPosition()`

Get the current player's position as x, y, z Score variables. Must be called in a player context (e.g., inside `forEveryPlayer`, individual variables updaters, or when `@s` is a player). This is the prefered way of checking a player's position.

```typescript
Actions.getCurrentPlayerPosition(): { x: Score; y: Score; z: Score }

// Example - Check if player is above Y=100:
const pos = Actions.getCurrentPlayerPosition();
_.if(pos.y.greaterThan(100), () => {
  Actions.announce({ message: "You reached the sky!" });
});

// Example - Store position in custom variables:
const { x, y, z } = Actions.getCurrentPlayerPosition();
variables.player_x.set(x);
variables.player_y.set(y);
variables.player_z.set(z);

// Example - Check if player is in a specific area:
const pos = Actions.getCurrentPlayerPosition();
_.if(_.and(
  pos.x.greaterThan(0),
  pos.x.lowerThan(100),
  pos.z.greaterThan(0),
  pos.z.lowerThan(100)
), () => {
  Actions.announce({ message: "You're in the zone!" });
});
```

**Note:** This returns integer coordinates (block position). The values are truncated from the player's exact floating-point position.

### Entities

#### `Actions.summonMultiple(params)`

Summon multiple entities at location.

```typescript
Actions.summonMultiple({
  entity: string;    // Entity ID (with "minecraft:" prefix)
  count: number;     // How many entities to summon
  x: number;         // X coordinate
  y: number;         // Y coordinate
  z: number;         // Z coordinate
  absolute: boolean; // true for absolute coords, false for relative
});

// Example:
Actions.summonMultiple({
  entity: "minecraft:zombie",
  count: 5,
  x: 0,
  y: 64,
  z: 0,
  absolute: true
});
```

#### `Actions.summonItem(params)`

Summon item entity at location.

```typescript
Actions.summonItem({
  item: string;      // Item ID (with "minecraft:" prefix)
  x: number;         // X coordinate
  y: number;         // Y coordinate
  z: number;         // Z coordinate
  absolute: boolean; // true for absolute coords, false for relative
});

// Example:
Actions.summonItem({
  item: "minecraft:diamond",
  x: 0,
  y: 64,
  z: 0,
  absolute: true
});
```

#### `Actions.kill(params)`

Kill entities matching selector.

```typescript
Actions.kill({
  selector: TargetNames;  // "all", "self", or any selector
});

// Examples:
Actions.kill({ selector: Selector("@e", { type: "minecraft:zombie" }) });
Actions.kill({ selector: Selector("@e", { type: "!minecraft:player" }) });
Actions.kill({ selector: "all" });  // Kill all participants
```

#### `Actions.teleport(params)`

Teleport entities to a location.

```typescript
Actions.teleport({
  target: TargetNames;  // "all", "self", or any selector
  x: number;            // X coordinate
  y: number;            // Y coordinate
  z: number;            // Z coordinate
  absolute: boolean;    // true for absolute coords, false for relative
});

// Examples:
Actions.teleport({ target: "self", x: 0, y: 100, z: 0, absolute: true });
Actions.teleport({ target: "all", x: 0, y: 64, z: 0, absolute: true });
Actions.teleport({ target: "self", x: 10, y: 0, z: 5, absolute: false });  // Relative position
```

### World

#### `Actions.setBlock(params)`

Set a single block.

```typescript
Actions.setBlock({
  block: string;     // Block ID (with "minecraft:" prefix)
  x: number;         // X coordinate
  y: number;         // Y coordinate
  z: number;         // Z coordinate
  absolute: boolean; // true for absolute coords, false for relative
});

// Example:
Actions.setBlock({
  block: "minecraft:diamond_block",
  x: 0,
  y: 64,
  z: 0,
  absolute: true
});
```

#### `Actions.fill(params)`

Fill region with blocks.

```typescript
Actions.fill({
  block: string;     // Block ID (with "minecraft:" prefix)
  x1: number;        // Start X coordinate
  y1: number;        // Start Y coordinate
  z1: number;        // Start Z coordinate
  x2: number;        // End X coordinate
  y2: number;        // End Y coordinate
  z2: number;        // End Z coordinate
  absolute: boolean; // true for absolute coords, false for relative
  mode: "fill" | "line" | "pyramid";  // Fill mode
});

// Examples:
Actions.fill({
  block: "minecraft:stone",
  x1: 0, y1: 64, z1: 0,
  x2: 10, y2: 64, z2: 10,
  absolute: true,
  mode: "fill"
});

Actions.fill({
  block: "minecraft:gold_block",
  x1: 0, y1: 64, z1: 0,
  x2: 0, y2: 10, z2: 0,
  absolute: true,
  mode: "pyramid"  // Builds a pyramid
});
```

#### `Actions.setTime(params)`

Set world time.

```typescript
Actions.setTime({
  time: "day" | "night" | number;  // Named or tick value
});

// Examples:
Actions.setTime({ time: "day" });
Actions.setTime({ time: 6000 });  // Noon
```

#### `Actions.gamerule(params)`

Set a gamerule.

```typescript
Actions.gamerule({
  rule: string;
  value: boolean | number;
});

// Examples:
Actions.gamerule({ rule: "doDaylightCycle", value: false });
Actions.gamerule({ rule: "mobGriefing", value: false });
Actions.gamerule({ rule: "randomTickSpeed", value: 0 });
```

### Scores

#### `Actions.set(params)`

Set score to value or copy from another score.

```typescript
// Set to number
Actions.set({
  variable: Score;
  value: number | Score;
});

// Examples:
Actions.set({ variable: variables.main_score, value: 0 });
Actions.set({ variable: variables.main_score, value: variables.diamonds });
```

#### `Actions.increment(params)`

Add 1 to score.

```typescript
Actions.increment({
  variable: Score;
});

// Example:
Actions.increment({ variable: variables.counter });
```

#### `Actions.decrement(params)`

Subtract 1 from score.

```typescript
Actions.decrement({
  variable: Score;
});

// Example:
Actions.decrement({ variable: variables.counter });
```

### Player Attributes

#### `Actions.setAttribute(params)`

Set entity attribute for a target.

```typescript
Actions.setAttribute({
  target: TargetNames;  // "all", "self", or any selector
  attribute_: string;   // Attribute name
  value: number;        // Attribute value
});

// Examples:
Actions.setAttribute({ target: "self", attribute_: "generic.max_health", value: 40 });
Actions.setAttribute({ target: "all", attribute_: "generic.movement_speed", value: 0.2 });
Actions.setAttribute({ target: "self", attribute_: "generic.attack_damage", value: 10 });
```

Common attributes (with `generic.` prefix):
- `"generic.max_health"` - Maximum HP (default 20)
- `"generic.movement_speed"` - Walk speed (default 0.1)
- `"generic.attack_damage"` - Base attack damage
- `"generic.armor"` - Armor points
- `"generic.knockback_resistance"` - Knockback resistance (0-1)


### Logging

#### `Actions.log_variable(params)`

Log variable to watcher system (debugging).

```typescript
Actions.log_variable({
  message: string;   // Log message
  variable: Score;   // Variable to log
  store: boolean;    // Whether to store in backend
});

// Example:
Actions.log_variable({
  message: "Player score",
  variable: variables.main_score,
  store: true
});
```

---

## Utilities

### `forEveryPlayer(callback)`

Execute code for each participant at their location.

```typescript
import { forEveryPlayer } from "@kradle/challenges";

forEveryPlayer(() => {
  // Runs as(@s) at(@s) for each participant
  // @s is the current player
  // All individual variables reference the current player within this context
});
```

**Important Notes:**
- Individual variables automatically reference the current player (`@s`) within the loop
- Global variables remain global and are the same across all iterations
- Each iteration executes at the player's position (`at(@s)`)

**Example - Find maximum score:**
```typescript
max_score: {
  type: "global",
  updater: (value, { main_score }) => {
    value.set(0);
    forEveryPlayer(() => {
      // main_score here refers to the current player's main_score
      _.if(main_score.greaterThan(value), () => {
        value.set(main_score);
      });
    });
  },
}
```

### Constants

```typescript
import { ALL, KRADLE_PARTICIPANT_TAG, WINNER_TAG } from "@kradle/challenges";

// ALL - Selector for all participants: @a[tag=kradle_participant]
// KRADLE_PARTICIPANT_TAG - Tag name: "kradle_participant"
// WINNER_TAG - Tag name: "kradle_winner"
```

---

## Sandstone Integration

This package is built on Sandstone. You can use Sandstone APIs directly:

```typescript
import { _, execute, Selector, rel, abs, MCFunction } from "sandstone";
```

### Conditions with `_`

```typescript
// Single condition
_.if(score.greaterThan(10), () => {
  // actions
});

// Combined conditions
_.if(_.and(
  score1.greaterThan(5),
  score2.equalTo(1)
), () => {
  // actions
});

_.if(_.or(
  condition1,
  condition2
), () => {
  // actions
});

// Block check
_.if(_.block(rel(0, -1, 0), "minecraft:diamond_block"), () => {
  // Player standing on diamond block
});
```

### Execute Commands

```typescript
// Store result in score
execute.as("@s").store.result.score(myScore).run.data.get.entity("@s", "Pos[1]");

// Run at location
execute.at("@s").run.particle("minecraft:flame", rel(0, 1, 0));

// Conditional execution
execute.if.score(myScore, ">=", 10).run.say("High score!");
```

### Selectors

```typescript
import { Selector } from "sandstone";

// With arguments
Selector("@a", { tag: "my_tag" });
Selector("@e", { type: "zombie", limit: 1, sort: "nearest" });

// NBT check
Selector("@s", {
  nbt: { Inventory: [{ id: "minecraft:diamond" }] }
});
```

### Relative/Absolute Coordinates

```typescript
import { rel, abs } from "sandstone";

rel(0, 1, 0);   // ~ ~1 ~
abs(0, 64, 0);  // 0 64 0
```

---

## Complete Examples

### Example 1: Speed Challenge - First to Kill 2 Pigs

```typescript
import { createChallenge, Actions, forEveryPlayer } from "@kradle/challenges";
import { _ } from "sandstone";

createChallenge({
  name: "pig-farming",
  kradle_challenge_path: "./output",
  roles: ["farmer"] as const,
  GAME_DURATION: 2 * 60 * 20,  // 2 minutes
  custom_variables: {
    pigs_farmed: {
      type: "individual",
      objective_type: "minecraft.killed:minecraft.pig",
      default: 0,
      updater: (value, { main_score }) => {
        main_score.set(value);
      },
    },
    game_over: {
      type: "global",
      updater: (value, { pigs_farmed }) => {
        value.set(0);
        forEveryPlayer(() => {
          _.if(pigs_farmed.greaterOrEqualThan(2), () => {
            value.set(1);
          });
        });
      },
    },
  },
})
  .events(() => ({
    start_challenge: () => {
      Actions.setTime({ time: "day" });
      Actions.announce({ message: "First to kill 2 pigs wins!" });
    },
    init_participants: () => {
      Actions.give({ target: "all", item: "minecraft:iron_sword", count: 1 });
    },
  }))
  .custom_events(({ pigs_farmed }) => [
    {
      score: pigs_farmed,
      target: 1,
      mode: "fire_once",
      actions: () => {
        Actions.announce({ message: "First pig down!" });
      },
    },
  ])
  .end_condition(({ game_over }) => game_over.equalTo(1))
  .win_conditions(({ pigs_farmed }, { farmer }) => ({
    [farmer]: pigs_farmed.greaterOrEqualThan(2),
  }));
```

### Example 2: Climb Challenge - Reach Highest Point

```typescript
import { createChallenge, Actions, forEveryPlayer } from "@kradle/challenges";
import { _, execute } from "sandstone";

createChallenge({
  name: "climb",
  kradle_challenge_path: "./output",
  roles: ["climber"] as const,
  GAME_DURATION: 3 * 60 * 20,
  custom_variables: {
    current_height: {
      type: "individual",
      objective_type: "dummy",
      updater: (value) => {
        value.set(Actions.getCurrentPlayerPosition().y)
      },
    },
    max_height: {
      type: "individual",
      updater: (value, { current_height, main_score }) => {
        _.if(current_height.greaterThan(value), () => {
          value.set(current_height);
        });
        main_score.set(value);
      },
    },
    max_height_global: {
      type: "global",
      updater: (value, { max_height }) => {
        value.set(0);
        forEveryPlayer(() => {
          _.if(max_height.greaterThan(value), () => {
            value.set(max_height);
          });
        });
      },
    },
    is_winner: {
      type: "individual",
      updater: (value, { max_height, max_height_global, has_never_died }) => {
        value.set(0);
        _.if(_.and(
          max_height.equalTo(max_height_global),
          max_height.greaterThan(0),
          has_never_died.equalTo(1)
        ), () => {
          value.set(1);
        });
      },
    },
  },
})
  .events(() => ({
    start_challenge: () => {
      Actions.setTime({ time: "day" });
      Actions.announce({ message: "Climb as high as you can!" });
    },
    init_participants: () => {
      Actions.give({ target: "all", item: "minecraft:cobblestone", count: 64 });
      Actions.give({ target: "all", item: "minecraft:cobblestone", count: 64 });
    },
  }))
  .custom_events(() => [])
  .end_condition(({ game_timer }) => game_timer.greaterThan(3 * 60 * 20))
  .win_conditions(({ is_winner }, { climber }) => ({
    [climber]: is_winner.equalTo(1),
  }));
```

### Example 3: Battle Royale - Last Player Standing

```typescript
import { createChallenge, Actions } from "@kradle/challenges";
import { _ } from "sandstone";

createChallenge({
  name: "battle-royale",
  kradle_challenge_path: "./output",
  roles: ["fighter"] as const,
  GAME_DURATION: 5 * 60 * 20,
  custom_variables: {
    kills: {
      type: "individual",
      objective_type: "playerKillCount",
      default: 0,
      updater: (value, { main_score }) => {
        main_score.set(value);
      },
    },
    sole_survivor: {
      type: "individual",
      updater: (value, { alive_players, has_never_died }) => {
        value.set(0);
        _.if(_.and(
          alive_players.equalTo(1),
          has_never_died.equalTo(1)
        ), () => {
          value.set(1);
        });
      },
    },
  },
})
  .events(() => ({
    start_challenge: () => {
      Actions.setTime({ time: "day" });
      Actions.gamerule({ rule: "naturalRegeneration", value: false });
      Actions.announce({ message: "Last player standing wins!" });
    },
    init_participants: () => {
      Actions.give({ target: "all", item: "minecraft:stone_sword", count: 1 });
      Actions.give({ target: "all", item: "minecraft:leather_chestplate", count: 1 });
      Actions.give({ target: "all", item: "minecraft:cooked_beef", count: 10 });
    },
  }))
  .custom_events(({ kills }) => [
    {
      score: kills,
      target: 1,
      mode: "fire_once",
      actions: () => {
        Actions.announce({ message: "First blood!" });
      },
    },
  ])
  .end_condition(({ alive_players }) => alive_players.equalTo(1))
  .win_conditions(({ sole_survivor }, { fighter }) => ({
    [fighter]: sole_survivor.equalTo(1),
  }));
```

### Example 4: Team-Based - Hunters vs Protectors

```typescript
import { createChallenge, Actions, forEveryPlayer } from "@kradle/challenges";
import { _ } from "sandstone";

createChallenge({
  name: "pig-farming-v2",
  kradle_challenge_path: "./output",
  roles: ["pighunter", "pigsaver"] as const,
  GAME_DURATION: 2 * 60 * 20,
  custom_variables: {
    pigs_killed: {
      type: "individual",
      objective_type: "minecraft.killed:minecraft.pig",
      default: 0,
      updater: (value, { main_score }) => {
        main_score.set(value);
      },
    },
    pig_killed_max: {
      type: "global",
      updater: (value, { pigs_killed }) => {
        value.set(0);
        forEveryPlayer(() => {
          _.if(pigs_killed.greaterThan(value), () => {
            value.set(pigs_killed);
          });
        });
      },
    },
  },
})
  .events((vars, { pighunter, pigsaver }) => ({
    start_challenge: () => {
      Actions.setTime({ time: "day" });
      Actions.announce({ message: "Hunters: Kill 2 pigs! Protectors: Stop them!" });
    },
    init_participants: () => {
      // Different items based on role would be set via role-specific logic
      Actions.give({ target: "all", item: "minecraft:wooden_sword", count: 1 });
    },
  }))
  .custom_events(() => [])
  .end_condition(({ pig_killed_max }) => pig_killed_max.greaterOrEqualThan(2))
  .win_conditions(({ pig_killed_max }, { pighunter, pigsaver }) => ({
    [pighunter]: pig_killed_max.greaterOrEqualThan(2),
    [pigsaver]: pig_killed_max.lowerThan(2),
  }));
```

### Example 5: Capture the Flag

```typescript
import { createChallenge, Actions } from "@kradle/challenges";
import { _, Selector, rel } from "sandstone";
import type { Score } from "sandstone";

createChallenge({
  name: "capture-the-flag",
  kradle_challenge_path: "./output",
  roles: ["red_team", "blue_team"] as const,
  GAME_DURATION: 5 * 60 * 20,
  custom_variables: {
    holds_red_banner: {
      type: "individual",
      updater: (value: Score) => {
        value.set(0);
        _.if(Selector("@s", {
          nbt: { Inventory: [{ id: "minecraft:red_banner" }] }
        }), () => {
          value.set(1);
        });
      },
    },
    stands_blue_wool: {
      type: "individual",
      updater: (value: Score) => {
        value.set(0);
        _.if(_.block(rel(0, -1, 0), "minecraft:blue_wool"), () => {
          value.set(1);
        });
      },
    },
    captured_flag: {
      type: "individual",
      updater: (value, { holds_red_banner, stands_blue_wool, main_score }) => {
        value.set(0);
        _.if(_.and(
          holds_red_banner.equalTo(1),
          stands_blue_wool.equalTo(1)
        ), () => {
          value.set(1);
          main_score.set(1);
        });
      },
    },
  },
})
  .events(() => ({
    start_challenge: () => {
      Actions.announce({ message: "Capture the enemy flag and return to base!" });
    },
    init_participants: () => {
      Actions.give({ target: "all", item: "minecraft:iron_sword", count: 1 });
    },
  }))
  .custom_events(({ captured_flag }) => [
    {
      score: captured_flag,
      target: 1,
      mode: "fire_once",
      actions: () => {
        Actions.announce({ message: "Flag captured! Game over!" });
      },
    },
  ])
  .end_condition(({ captured_flag }) => captured_flag.equalTo(1))
  .win_conditions(({ captured_flag }, { red_team, blue_team }) => ({
    [red_team]: captured_flag.equalTo(0),
    [blue_team]: captured_flag.equalTo(1),
  }));
```

---

## Common Patterns

### Pattern 1: Sync Variable to Main Score

```typescript
my_variable: {
  type: "individual",
  objective_type: "some_criterion",
  updater: (value, { main_score }) => {
    main_score.set(value);
  },
}
```

### Pattern 2: Find Global Maximum

```typescript
max_global: {
  type: "global",
  updater: (value, { individual_score }) => {
    value.set(0);
    forEveryPlayer(() => {
      _.if(individual_score.greaterThan(value), () => {
        value.set(individual_score);
      });
    });
  },
}
```

### Pattern 3: Winner Detection (Has Max Score + Alive)

```typescript
is_winner: {
  type: "individual",
  updater: (value, { main_score, max_global, has_never_died }) => {
    value.set(0);
    _.if(_.and(
      main_score.equalTo(max_global),
      main_score.greaterThan(0),
      has_never_died.equalTo(1)
    ), () => {
      value.set(1);
    });
  },
}
```

### Pattern 4: Track Player Position

```typescript
current_y: {
  type: "individual",
  objective_type: "dummy",
  updater: (value) => {
    // Prefered way: using the dedicated Action
    value.set(Actions.getCurrentPlayerPosition().y)

    // Alternative way: using execute.store + data.get
    execute.as("@s").store.result.score(value).run.data.get.entity("@s", "Pos[1]");
  },
}
```

### Pattern 5: Check Inventory for Item

```typescript
has_diamond: {
  type: "individual",
  updater: (value: Score) => {
    value.set(0);
    _.if(Selector("@s", {
      nbt: { Inventory: [{ id: "minecraft:diamond" }] }
    }), () => {
      value.set(1);
    });
  },
}
```

### Pattern 6: Check Block Below Player

```typescript
on_gold_block: {
  type: "individual",
  updater: (value: Score) => {
    value.set(0);
    _.if(_.block(rel(0, -1, 0), "minecraft:gold_block"), () => {
      value.set(1);
    });
  },
}
```

### Pattern 7: Count Entities

```typescript
zombie_count: {
  type: "global",
  updater: (value) => {
    value.set(0);
    execute.as(Selector("@e", { type: "zombie" })).run(() => {
      value.add(1);
    });
  },
}
```

### Pattern 8: Simple End Condition

```typescript
.end_condition(({ objective_complete }) => objective_complete.equalTo(1))
```

### Pattern 9: Multi-Condition End

```typescript
.end_condition(({ alive_players, objective_complete }) =>
  _.or(
    alive_players.equalTo(1),
    objective_complete.equalTo(1)
  )
)
```

### Pattern 10: Opposing Team Win Conditions

```typescript
.win_conditions(({ team_a_score, team_b_score }, { team_a, team_b }) => ({
  [team_a]: team_a_score.greaterThan(team_b_score),
  [team_b]: team_b_score.greaterThan(team_a_score),
}))
```

---

## Tips for LLMs

1. **Always use `as const`** for roles array to get proper type inference
2. **Updaters should be idempotent** - they run every tick
3. **Use `forEveryPlayer` for global aggregations** (max, count, any/all checks)
4. **Import `_` from sandstone** for conditions (`_.if`, `_.and`, `_.or`)
5. **Variables are Scores** - use `.set()`, `.add()`, comparison methods
6. **Main score is displayed** - sync your primary metric to `main_score`
7. **Time is in ticks** - multiply seconds by 20
8. **Win conditions are per-role** - each role needs its own condition
9. **Custom events fire per-player** for individual variables
