> ## Documentation Index
> Fetch the complete documentation index at: https://docs.summerengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Your First Crafty Game

> Golden path: integrate Crafty platform APIs into a simple game, then test, export, and submit.

## What You Build

A minimal multiplayer integration that:

* extends `CraftyGame`,
* uses server-authoritative gameplay updates,
* syncs player-visible state with `set_synced`,
* passes local testing and submission pipeline.

## Step 1: Create `manifest.json`

```json theme={null}
{
  "id": "first-crafty-game",
  "name": "First Crafty Game",
  "version": "1.0.0",
  "crafty_sdk": "1.0",
  "entry_scene": "main.tscn",
  "player_scene": "player.tscn",
  "min_players": 1,
  "max_players": 8
}
```

## Step 2: Choose Your Player Path

### 3D Action Template (fastest start)

* set `player.tscn` root script to `res://sdk/crafty_character_3d.gd`
* use `apply_default_movement(...)` from `CraftyGame`.

### Custom Genre Path (2D/card/RTS/puzzle)

* make your own script extending `CraftyPlayer`
* sync your own state model (`set_synced("hand_count", ...)`, etc.).

## Step 3: Build `main.gd`

```gdscript theme={null}
extends CraftyGame

const ROUND_SECONDS := 120.0
const MOVE_SPEED := 7.0
const GRAVITY := 20.0

func get_prediction_params() -> Dictionary:
    return {"move_speed": MOVE_SPEED, "gravity": GRAVITY}

func _game_init() -> void:
    var spawn_root := get_node_or_null("SpawnPoints")
    if spawn_root:
        for child in spawn_root.get_children():
            if child is Node3D:
                spawn_points.append(child.global_position)

func _game_start() -> void:
    Crafty.set_time_limit(ROUND_SECONDS)
    Crafty.send_announcement("Round started")

func _game_end() -> void:
    Crafty.send_announcement("Game over")

func _player_joined(player) -> void:
    if player.has_method("respawn"):
        player.respawn(get_random_spawn_point())
    player.set_synced("score", 0)

func _player_left(_player) -> void:
    pass

func _process(delta: float) -> void:
    super._process(delta)
    if not Crafty.is_server():
        return
    for p in get_players():
        if p.has_method("respawn"):
            apply_default_movement(p, delta, MOVE_SPEED, GRAVITY)
```

## Step 4: Add One Multiplayer Rule

Example scoring rule:

```gdscript theme={null}
func score_point(player) -> void:
    var current := int(player.get_synced("score") if player.get_synced("score") != null else 0)
    current += 1
    player.set_synced("score", current)
    Crafty.send_announcement("%s scored (%d)" % [str(player.get("display_name")), current])
```

## Step 5: Local Test

Use the two-process test runner flow:

* starts local headless server,
* starts client and connects to localhost,
* validates real multiplayer behavior.

Guide:

* [/api-reference/crafty-sdk/testing-your-game-locally](/api-reference/crafty-sdk/testing-your-game-locally)

## Step 6: Export and Upload

Export a game-only `.pck`, then upload with `manifest.json`.

Guides:

* [/api-reference/crafty-sdk/exporting-and-uploading-your-game](/api-reference/crafty-sdk/exporting-and-uploading-your-game)
* [/api-reference/crafty-sdk/submission-guide](/api-reference/crafty-sdk/submission-guide)

## What “Done” Looks Like

* [ ] All lifecycle hooks implemented.
* [ ] Server authority enforced (`Crafty.is_server()`).
* [ ] Player-visible state comes from `set_synced`.
* [ ] Local test runner path passes.
* [ ] Upload passes scanner + enters review.
