TypeScript · Node.js · Pixel Worlds

A bot framework for
Pixel Worlds

Handles the full lifecycle — authentication via Steam, Android, or email, TCP socket communication with game servers, world parsing, A* pathfinding, and in-world actions.

TypeScript 5 Node.js 18+ BSON · Zstd A* Pathfinding SOCKS5 Proxy

Project Structure

All source code lives under src/, split into core logic, API clients, and utility helpers.

⚙️
core/
session.ts — auth lifecycle
bot.ts — TCP client + actions
world.ts — tile grid parser
🔌
clients/
playfab_client.ts — REST login
socialfirst_client.ts — token exchange
🛠️
utils/
packets · pathfinder · logger · fetch_with_retry · queue · buffer · zstd
📐
types/
All shared TypeScript interfaces, types, and enums in one place.
file tree
pixel-worlds/
├── src/
│   ├── index.ts                 # Entry point
│   ├── config.ts                # App configuration
│   ├── types/index.ts           # Shared types & enums
│   ├── core/
│   │   ├── session.ts           # Auth + session bootstrap
│   │   ├── bot.ts               # TCP client, packet I/O, actions
│   │   └── world.ts             # World deserialization + tile queries
│   ├── clients/
│   │   ├── playfab_client.ts    # PlayFab REST (Steam/Android/Email)
│   │   └── socialfirst_client.ts  # SocialFirst token exchange
│   └── utils/
│       ├── packets.ts           # BSON packet builders
│       ├── pathfinder.ts        # A* pathfinding
│       ├── logger.ts            # Namespaced terminal logger
│       ├── fetch_with_retry.ts  # HTTP + SOCKS5 + retry
│       ├── conqurent_queue.ts   # Concurrency-limited task queue
│       ├── receive_buffer.ts    # TCP stream reassembly
│       └── zstd_service.ts      # Zstandard decompression
├── tsconfig.json
└── package.json

Architecture

Three layered stages: authenticate credentials, establish a TCP connection, then act inside a world.

index.ts └── ConcurrentQueue (N threads) └── PixelWorldsSession ├── SteamUser ─── npm: steam-user ├── PlayFabClient ──► PlayFab REST API ├── SocialFirstClient ──► SocialFirst REST API └── Bot ├── TCP Socket ──► game server :10001 ├── ReceiveBuffer stream reassembly ├── World tile grid + collectables └── Pathfinder A* over fore_tiles
ℹ️ The ConcurrentQueue controls how many sessions run in parallel. Set threads in config to control concurrency. Each session spawns its own Steam client, API clients, and Bot instance — they share no state.

Installation

Requires Node.js 18+ and TypeScript 5+. Install dependencies, configure your environment, then run.

1
npm install
Installs steam-user, zstd-codec, tsx, typescript, and type definitions.
2
cp .env.example .env && nano .env
Fill in your Steam / email credentials and API keys.
3
npm run dev
Runs directly with tsx — no compile step needed for development.
4
npm run build && npm start
Compile to dist/ and run the production build.
.env
# Concurrency
THREADS=5

# Steam login
STEAM_ACCOUNT=your_username
STEAM_PASSWORD=your_password

# Android login
ANDROID_DEVICE_ID=

# Email login
PW_EMAIL=you@example.com
PW_EMAIL_PASSWORD=your_password

# API
SF_API_KEY=your_key

# Optional SOCKS5 proxy
PROXY_URL=socks5://127.0.0.1:1080

Configuration

All config is typed via the Config interface. Use environment variables — never hardcode secrets.

FieldTypeDescription
threadsnumberMax concurrent sessions in ConcurrentQueue
steam_accountstringSteam username
steam_passwordstringSteam password
android_device_idstring?Android device UUID for Android login
emailstring?Account email for email login
email_passwordstring?Account email password
user_agentstringUnity HTTP user-agent string
unity_versionstringUnity version header sent with all requests
sf_api_keystringSocialFirst client API key
proxy_urlstring?SOCKS5 proxy URL e.g. socks5://host:port
urlsobjectPlayFab & SocialFirst endpoint URLs
typescript · config.ts
import { Config } from './types';

export const CONFIG: Config = {
    threads:        Number(process.env.THREADS ?? 1),
    steam_account:  process.env.STEAM_ACCOUNT  ?? '',
    steam_password: process.env.STEAM_PASSWORD ?? '',
    android_device_id: process.env.ANDROID_DEVICE_ID ?? '',
    email:          process.env.PW_EMAIL          ?? '',
    email_password: process.env.PW_EMAIL_PASSWORD ?? '',
    user_agent:     'UnityPlayer/6000.3.11f1 ...',
    unity_version:  '6000.3.11f1',
    sf_api_key:     process.env.SF_API_KEY ?? '',
    proxy_url:      process.env.PROXY_URL,
    urls: { /* PlayFab + SocialFirst endpoints */ },
};

Authentication Flow

Three login methods are supported. All ultimately exchange for a SocialFirst JWT used to authenticate the TCP connection.

1
Login via Steam / Android / Email
Steam: logs into Steam, ensures Pixel Worlds ownership (auto-requests free license if missing), then creates an auth session ticket.
Android: uses a normalized device UUID directly.
Email: uses PlayFab email/password credentials.
PlayFabClient.login_with_*()
2
PlayFab → SessionTicket
The login method posts to the PlayFab REST API, which returns a SessionTicket and EntityToken.
→ returns SessionTicket
3
SocialFirst Token Exchange
init_account() initializes the account on SocialFirst's auth service, then exchange_token() swaps the PlayFab token for a socialFirstToken (JWT).
→ returns JWT
4
TCP Login Packet
The JWT is passed to Bot.set_credentials(jwt), then Bot.connect(host). On connection, the bot immediately sends a make_login(jwt) BSON packet to the game server.
→ status: AUTHENTICATING → ONLINE

PixelWorldsSession

Manages the complete authentication lifecycle from credentials to a connected Bot.

typescript
import { PixelWorldsSession } from './core/session';
import { CONFIG } from './config';

const session = new PixelWorldsSession(CONFIG);

// Returns { device_id, sf_token }
const result = await session.start('android');
// or 'steam' | 'email'
async
start(type: 'steam' | 'android' | 'email')
Runs the full auth flow. Returns { device_id, sf_token } or connects the bot and resolves when in-world.

Bot

The TCP client and action layer. Extends TypedEmitter<BotEvents> for type-safe event handling.

Status Lifecycle

OFFLINE AUTHENTICATING ONLINE MENU_READY IN_WORLD REDIRECTING

Connection

sync
set_credentials(jwt: string)
Set the SocialFirst JWT before connecting.
async
connect(host: string)
Opens TCP connection to the game server on port 10001. Auto-reconnects on disconnect (up to 10 attempts, 1–30s backoff).
sync
set_proxy(host, port, user?, pass?)
Route the TCP game connection through a SOCKS5 proxy.
sync
stop()
Destroys the socket, clears timers, resets position and world state.

Movement

async
walk_to_map(target_x, target_y, cancelled?)
Walk to an absolute tile coordinate using A* pathfinding. Falls back to straight-line if no path found.
async
walk_to_map_point(steps: Position[], cancelled?)
Walk a pre-computed array of tile positions, with correct animation states (walk, jump, fall).
async
walk_predefined_path(steps: Position[])
Walk a manually defined path without pathfinding computation.
async
manual_move(direction: 'left'|'right'|'up'|'down')
Move exactly one tile in a direction. Checks walkability before moving.
async
wait_for_map_position(x, y, tolerance?, timeout_ms?)
Polls until bot reaches the given tile position within tolerance. Default timeout: 10s.

Block Interaction

async
manual_punch(offset_x, offset_y)
Punch the tile at an offset from the bot's current position. Automatically punches background if foreground is empty.
async
manual_place(offset_x, offset_y, block_id)
Place a block at an offset from current position.

Chat

async
send_world_chat(message: string)
Send a message in the current world's chat.
async
spam_loop(message, delay_ms)
Repeatedly send a message at a given interval. Call stop_spam() to halt.

Events

event
bot.on('status', ({old_status, new_status, bot}) => {})
Fires on every status transition.
event
bot.on('status:in_world', (bot) => {})
Fires when fully spawned in a world — safe to start acting here.
event
bot.on('status:auth_failed', (bot) => {})
Fires when JWT authentication is rejected by the server.

State Properties

bot.posPositionCurrent map-grid tile position {x, y}
bot.directionnumberCurrent facing — DIR_LEFT (7) or DIR_RIGHT (3)
bot.statusBotStatusCurrent lifecycle status enum value
bot.namestringBot's in-game player name (set after login)
bot.uidstringBot's player UID
bot.worldWorldAttached World instance with full tile data
bot.ping_msnumberLast measured round-trip ping in milliseconds
bot.is_in_worldbooleanTrue when fully spawned and in a world

World

Parses and holds the world's tile grid, collectables, and interactive items. Data is Zstd-decompressed and BSON-deserialized from the server packet.

Tile Queries

sync
get_tile(x, y): Tile | null
Returns the tile at grid coordinates. Returns null for out-of-bounds or the top 3 inaccessible rows.
sync
for_each(cb: (tile, x, y) => void)
Iterate all accessible tiles. Skips the top 3 rows automatically.
sync
find_by_fore(id: number): [number, number][]
Find all tile positions with a specific foreground block ID. Returns array of [x, y] tuples.
sync
get_world_item(x, y): WorldItem | undefined
Get a placed interactive item at a tile position.
sync
find_open_portal(): WorldItem | undefined
Finds the first world item with class PortalData.

Properties

world.namestringWorld name (e.g. START, ABCPIKE)
world.size_xnumberWidth of the world in tiles
world.size_ynumberHeight of the world in tiles
world.main_door_x/ynumberSpawn point tile coordinates
world.tilesTile[]Full flat tile array (size_x × size_y)
world.fore_tilesUint16ArrayForeground block IDs — used directly by the A* pathfinder
world.collectablesCollectable[]All dropped items and gems in the world
world.gem_collectablesCollectable[]Filtered subset — only gem collectables
world.world_itemsWorldItem[]All placed interactive items (portals, locks, etc.)
typescript · tile structure
interface Tile {
    fore_id: number;       // foreground block ID (0 = air)
    back_id: number;       // background block ID
    pos: { x: number; y: number };
    extra?: WorldItem;    // interactive item on this tile (if any)
}

Utilities

Shared helpers used across the codebase.

📦
packets.ts
BSON packet builders for all game actions. Exports encode_packet, decode_packet, coordinate converters, and every packet constructor (make_login, make_movement_packet, make_hit_block, etc.).
🗺️
pathfinder.ts
A* pathfinding over Uint16Array tile grids. Uses tile-cost weighting — solid blocks are impassable, scaffolding-type tiles have high cost. Returns Position[] | null.
🌐
fetch_with_retry.ts
HTTP client wrapping native fetch with configurable timeout, retry count, retry delay, and optional SOCKS5 proxy via socks-proxy-agent.
📋
conqurent_queue.ts
Concurrency-limited async task queue. Constructor takes concurrency limit. add(task) queues work; onIdle() resolves when all tasks finish.
📝
logger.ts
Namespaced, ANSI-coloured terminal logger with levels debug | info | warn | error | silent. Methods: debug, info, success, warn, error, step, divider.
zstd_service.ts
Thin wrapper around zstd-codec for decompressing Zstandard-compressed world data received from the game server.

Packet Constants

ConstantValueDescription
ANIM_IDLE1Standing still animation
ANIM_WALK2Walking animation
ANIM_JUMP3Jumping animation
ANIM_START_FALL4Beginning to fall
ANIM_FALL5Falling animation
ANIM_PUNCH6Punch / hit animation
DIR_RIGHT3Facing right direction
DIR_LEFT7Facing left direction
TILE_WIDTH0.32World-unit width of one tile
TILE_HEIGHT0.32World-unit height of one tile

Types & Enums

All shared types are defined in src/types/index.ts.

typescript · enums
enum BotStatus {
    AUTHENTICATING  = "authenticating",
    AUTH_FAILED     = "auth_failed",
    OFFLINE         = "offline",
    ONLINE          = "online",
    MENU_READY      = "menu_ready",
    AWAITING_READY  = "awaiting_ready",
    IN_WORLD        = "in_world",
    REDIRECTING     = "redirecting",
}

enum NetStatus {
    NO_SOCKET, IDLE, CONNECTING, CONNECTED, DISCONNECTED
}

type Login_Type = 'steam' | 'android' | 'email';
type Log_Level_Key = 'debug' | 'info' | 'warn' | 'error' | 'silent';
typescript · key interfaces
interface Position    { x: number; y: number }

interface Tile {
    fore_id: number;  back_id: number;
    pos: Position;    extra?: WorldItem;
}

interface WorldItem {
    class: string;   item_id: number;
    block_type: number;
    pos: Position;   data: Record<string, any>;
}

interface Collectable {
    collectable_id: number;  block_type: number;
    amount: number;          inventory_type: number;
    pos_x: number;           pos_y: number;
    is_gem: boolean;         gem_type: number;
}

Usage Example

A complete example: authenticate, connect, walk to a position, punch a block, and read the world.

typescript · full example
import { CONFIG } from './config';
import { PixelWorldsSession } from './core/session';
import { Bot } from './core/bot';

const session = new PixelWorldsSession(CONFIG);
const { sf_token } = await session.start('android');

const bot = new Bot();
bot.set_credentials(sf_token);

// Optional: route through SOCKS5 proxy
// bot.set_proxy('127.0.0.1', 1080);

bot.on('status:in_world', async (b) => {
    console.log(`Entered world: ${b.world.name}`);

    // Walk to tile (10, 30) via A* pathfinding
    await b.walk_to_map(10, 30);

    // Punch tile to the right
    await b.manual_punch(1, 0);

    // Place a dirt block above
    await b.manual_place(0, -1, 2);

    // Chat
    await b.send_world_chat('Hello!');

    // Read collectables
    const gems = b.world.gem_collectables;
    console.log(`Gems: ${gems.length}`);

    // Find all tiles with block ID 8 (e.g. lava)
    const positions = b.world.find_by_fore(8);

    b.stop();
});

await bot.connect('63.176.210.142');

Security Notes

Important practices to follow before deploying or committing code.

⚠️ Never hardcode credentials. Move all secrets — steam_password, email_password, sf_api_key — to a .env file and add .env to your .gitignore immediately. Exposed credentials in a zip or repository should be rotated right away.
🔒 World snapshots are written to disk. The files src/utils/world_raw.json and src/utils/world.json are overwritten every time a world is loaded at runtime. These may contain positional and item data. Add them to .gitignore as well.
🌐 Proxy support covers TCP too. bot.set_proxy() routes the raw game server TCP socket through the SOCKS5 proxy, in addition to the HTTP requests handled by fetch_with_retry via socks-proxy-agent.
File / PatternAction
.envAdd to .gitignore, never commit
src/utils/world*.jsonAdd to .gitignore (runtime output)
Hardcoded passwordsReplace with process.env.VAR
Exposed credentialsRotate immediately on all affected services
account.txtTreat as sensitive — restrict file permissions