Initial project scaffold: dataset schema, 31 seed training examples, Mineflayer bot framework, and 7-phase roadmap
- IDEA.md: project scope (Minecraft ops AI assistant via qwen3-coder LoRA/SFT) - PLAN.md: complete roadmap with prior art analysis, architecture, phased plan, dev server docs - data/schema.json: training example JSON Schema with negative_output support - data/processed/seed_dataset.jsonl: 31 validated examples from repair code, prayer logs, session history - data/validate_dataset.py: schema validator with summary statistics - ingame/: Mineflayer bot framework (test_connect, spawn_bots, aware_bots with full event logging) - Directory structure for knowledge/, eval/, training/, agent/ (Phase 1.3+ work)
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
*.egg
|
||||
dist/
|
||||
build/
|
||||
.venv/
|
||||
venv/
|
||||
*.so
|
||||
|
||||
# Node / Mineflayer
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# Training checkpoints (large binary files)
|
||||
training/checkpoints/
|
||||
*.bin
|
||||
*.safetensors
|
||||
*.pt
|
||||
*.gguf
|
||||
|
||||
# Eval results (regenerable)
|
||||
eval/results/
|
||||
|
||||
# Secrets and credentials
|
||||
GITEA_API.md
|
||||
*.env
|
||||
.env.*
|
||||
credentials.json
|
||||
*.key
|
||||
*.pem
|
||||
|
||||
# OS / editor
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Session-specific (kept locally, not shared)
|
||||
SESSION.md
|
||||
@@ -0,0 +1,15 @@
|
||||
# IDEA.md -- Project Idea
|
||||
|
||||
## What is this?
|
||||
A lightweight, Minecraft-specific AI assistant built by adapting `qwen3-coder` (not training from scratch). It should act as an operations copilot for Sethpc Minecraft servers: generating correct commands, troubleshooting logs, and safely automating repetitive admin tasks.
|
||||
|
||||
## Problem it solves
|
||||
General-purpose models carry broad knowledge and can feel heavy, expensive, and inconsistent for Minecraft server operations. This project narrows focus to Minecraft workflows so responses are faster, cheaper, and more accurate for tasks like RCON command generation, event parsing, world border automation, and 1.21 syntax correctness.
|
||||
|
||||
## Constraints / preferences
|
||||
- Base model: `qwen3-coder` (adapt/fine-tune with LoRA/SFT)
|
||||
- Scope: Minecraft ops assistant (not a full new foundation model)
|
||||
- Prioritize tool use + guardrails over memorization
|
||||
- Must understand local homelab server context and documented workflows
|
||||
- Prefer iterative approach: baseline -> dataset -> fine-tune -> eval -> deploy
|
||||
- Safety: command allowlists, destructive-action confirmations, auditable logs
|
||||
@@ -0,0 +1,390 @@
|
||||
# PLAN.md -- Project Roadmap (Live Document)
|
||||
|
||||
> **Last updated:** 2026-03-18 (rev 2)
|
||||
> **Status legend:** `[ ]` planned | `[~]` in progress | `[x]` done | `[-]` cancelled/deferred
|
||||
|
||||
---
|
||||
|
||||
## 0. Vision
|
||||
|
||||
Build a lightweight, Minecraft-focused AI assistant by adapting `qwen3-coder` (LoRA/SFT). The assistant operates as an **ops copilot** for Sethpc Minecraft servers -- generating correct commands, troubleshooting logs, automating admin tasks, and optionally acting as an **in-game AI character** for live interaction, training data collection, and evaluation.
|
||||
|
||||
This is **not** a gameplay agent (like Voyager/MineDojo). It is a **server operations assistant** with an optional embodied presence for testing and data gathering.
|
||||
|
||||
---
|
||||
|
||||
## 1. Prior Art & Inspirations
|
||||
|
||||
These projects informed the plan but solve different problems:
|
||||
|
||||
| Project | What it does | What we borrow |
|
||||
|---------|-------------|----------------|
|
||||
| **Voyager** (6.7k stars) | LLM-powered embodied agent that plays Minecraft via Mineflayer. Skill library + auto-curriculum + iterative prompting. | Skill library concept (reusable verified command sequences). Iterative self-verification loop for command correctness. |
|
||||
| **MineDojo** (2.2k stars) | RL/LLM research framework with 3142 tasks. Internet-scale knowledge base (730K YouTube vids, 7K wiki pages, 340K Reddit posts). | Knowledge corpus pipeline -- scraping wiki.vg and Minecraft Wiki for command syntax reference data. Task-based evaluation structure. |
|
||||
| **Mindcraft** (4.9k stars) | LLM + Mineflayer in-game bots with profiles, multi-agent collab. Supports Ollama, many APIs. | Profile-based bot architecture. In-game chat integration pattern. Ollama local model support. Provides own fine-tuned models (`sweaterdog/andy-4`). |
|
||||
| **minecraft-mcp-server** (514 stars) | MCP (Model Context Protocol) server wrapping Mineflayer. Lets Claude/LLMs control a Minecraft character via tool calls. | MCP tool-call interface for in-game actions. Could be adapted for our eval harness. |
|
||||
| **Mineflayer** (6.7k stars) | Node.js Minecraft bot framework. Supports 1.8-1.21.11. Movement, inventory, chat, block interaction. | Primary framework for in-game AI character. Mature, well-maintained, 1.21 support confirmed. |
|
||||
| **Existing AI God system** (our own) | Log-tail + RCON + Ollama pipeline. `pray` trigger, divine intervention, command validation, syntax repair. Vanilla + Paper fork. | Direct predecessor. Baseline to measure against. Source of real training data (prayer logs, bug reports). |
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture Overview
|
||||
|
||||
```
|
||||
+---------------------+
|
||||
| Minecraft Server |
|
||||
| (CT 644, 1.21.x) |
|
||||
+----+----------+-----+
|
||||
| |
|
||||
RCON | | Protocol (Mineflayer)
|
||||
| |
|
||||
+---------+--+ +---+------------+
|
||||
| Ops Layer | | In-Game Agent |
|
||||
| (existing | | (Mineflayer |
|
||||
| log-tail + | | bot, optional)|
|
||||
| RCON cmds) | +---+------------+
|
||||
+---------+--+ |
|
||||
| |
|
||||
+----+---------+----+
|
||||
| Assistant Core |
|
||||
| (qwen3-coder |
|
||||
| + LoRA adapter) |
|
||||
+----+----+---------+
|
||||
| |
|
||||
+--------+ +--------+
|
||||
| |
|
||||
+-----+------+ +---------+--------+
|
||||
| Tool Layer | | Knowledge/RAG |
|
||||
| - RCON exec | | - MC Wiki index |
|
||||
| - Log query | | - Command syntax |
|
||||
| - MCSManager| | - Server context |
|
||||
| API | | - Prior sessions |
|
||||
+-------------+ +------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Phased Roadmap
|
||||
|
||||
### Phase 1: Foundation (Weeks 1-3) -- HIGH DETAIL
|
||||
|
||||
> Goal: Repo setup, baseline tooling, dataset schema, knowledge corpus.
|
||||
|
||||
#### 1.1 Project Setup
|
||||
- [x] Define project idea and constraints (`IDEA.md`)
|
||||
- [x] Confirm no prior art exists for this specific niche
|
||||
- [x] Create `PLAN.md` (this document)
|
||||
- [x] Create Gitea repo and configure remote
|
||||
- [x] Set up directory structure:
|
||||
```
|
||||
Mincecraft-AI-model/
|
||||
├── PLAN.md
|
||||
├── IDEA.md
|
||||
├── SESSION.md # local only (gitignored)
|
||||
├── SESSION.default.md # template reference (tracked)
|
||||
├── .gitignore
|
||||
├── data/
|
||||
│ ├── raw/ # scraped wiki, logs, transcripts
|
||||
│ ├── processed/ # cleaned, formatted training pairs
|
||||
│ │ └── seed_dataset.jsonl # 31 seed examples
|
||||
│ ├── schema.json # dataset JSON Schema
|
||||
│ └── validate_dataset.py
|
||||
├── knowledge/
|
||||
│ ├── mc-commands/ # 1.21 command syntax reference
|
||||
│ ├── server-context/ # server.properties, datapacks, infra
|
||||
│ └── wiki-chunks/ # chunked wiki content for RAG
|
||||
├── eval/
|
||||
│ ├── tasks/ # evaluation task definitions
|
||||
│ └── results/ # scored outputs (gitignored)
|
||||
├── training/
|
||||
│ ├── configs/ # LoRA/SFT training configs
|
||||
│ ├── scripts/ # training launch scripts
|
||||
│ └── checkpoints/ # saved adapters (gitignored)
|
||||
├── agent/
|
||||
│ ├── tools/ # RCON, log query, MCSManager tools
|
||||
│ ├── guardrails/ # command allowlist, safety policies
|
||||
│ └── prompts/ # system prompts, few-shot templates
|
||||
└── ingame/ # in-game bots (Mineflayer)
|
||||
├── package.json
|
||||
├── test_connect.js # single bot connection test
|
||||
├── spawn_bots.js # multi-bot spawner (passive)
|
||||
└── aware_bots.js # event-aware bots (training data)
|
||||
```
|
||||
- [x] Add `.gitignore` (checkpoints, secrets, __pycache__, node_modules)
|
||||
- [x] Initial commit and push
|
||||
|
||||
#### 1.2 Dataset Schema
|
||||
- [x] Define the training example format (`data/schema.json`) -- includes negative_output for wrong->correct pairs
|
||||
- [x] Write a JSON Schema validator script (`data/validate_dataset.py`)
|
||||
- [x] Seed 31 examples from repair code, prayer logs, sudo logs, and session history (`data/processed/seed_dataset.jsonl`)
|
||||
|
||||
#### 1.3 Knowledge Corpus
|
||||
- [ ] Scrape Minecraft Wiki command reference pages for 1.21.x syntax
|
||||
- Target: `/give`, `/effect`, `/tp`, `/execute`, `/worldborder`, `/weather`, `/gamemode`, `/enchant`, `/fill`, `/setblock`, `/clone`, `/scoreboard`, `/data`, `/function`
|
||||
- Store as structured JSON (command, syntax, parameters, examples, version notes)
|
||||
- [ ] Extract and chunk local server context:
|
||||
- `server.properties` from mc1 and shrink-world
|
||||
- Datapack definitions (shrinkborder, morespawns)
|
||||
- Player list and UUID mappings
|
||||
- RCON connection parameters (sanitized)
|
||||
- [ ] Index knowledge corpus for RAG retrieval (simple TF-IDF or embedding-based)
|
||||
- [ ] Validate: query the index with sample questions, spot-check relevance
|
||||
|
||||
#### 1.4 Baseline Assistant (No Fine-Tuning)
|
||||
- [ ] Build prompt-only assistant using `qwen3-coder` (via Ollama at 192.168.0.179)
|
||||
- [ ] Implement tool-calling interface:
|
||||
- `rcon_execute(command)` -- send RCON command, return result
|
||||
- `query_log(pattern, lines)` -- search recent server log
|
||||
- `query_knowledge(question)` -- RAG lookup against knowledge corpus
|
||||
- `get_server_status()` -- player list, TPS, uptime via MCSManager API
|
||||
- [ ] Implement safety guardrails:
|
||||
- Command allowlist (whitelist known-safe command prefixes)
|
||||
- Destructive action confirmation (commands matching `/kill`, `/stop`, `/ban`, `/op`, `/fill`, `/worldborder set 0`)
|
||||
- Syntax validation (1.21 enchantment format, weather values, effect names)
|
||||
- Audit log (every command attempted + result, timestamped JSON)
|
||||
- [ ] Test baseline on 20 seed examples, record accuracy manually
|
||||
- [ ] Document baseline performance as the bar to beat
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Data Collection & Evaluation Framework (Weeks 3-5) -- MEDIUM DETAIL
|
||||
|
||||
> Goal: Build a proper eval suite and expand the dataset using real server interactions.
|
||||
|
||||
#### 2.1 Evaluation Suite
|
||||
- [ ] Define task categories:
|
||||
- **Command generation** -- "Give player X netherite sword with sharpness 5" -> correct `/give` command
|
||||
- **Troubleshooting** -- "Server is lagging" + log excerpt -> diagnosis + recommended actions
|
||||
- **Automation** -- "Shrink border by 10 every time someone dies" -> datapack/script plan
|
||||
- **Information** -- "What enchantments work on tridents in 1.21?" -> accurate answer
|
||||
- **Safety** -- "Delete the world" -> refusal or confirmation gate
|
||||
- [ ] Write 50+ evaluation tasks across categories (target: 100 eventually)
|
||||
- [ ] Build evaluation harness (`eval/harness.py`):
|
||||
- Loads task definitions
|
||||
- Runs each through the assistant
|
||||
- Scores: command syntax correctness (parseable?), factual accuracy, safety compliance, hallucination detection
|
||||
- Outputs scored results as JSON + summary report
|
||||
- [ ] Run baseline evaluation, establish benchmark scores
|
||||
|
||||
#### 2.2 Data Expansion
|
||||
- [ ] Extract training pairs from existing AI God prayer logs on CT 644
|
||||
- Parse `/var/log/mc_aigod_*.log` and prayer history
|
||||
- Convert to dataset schema format
|
||||
- Label quality: validated/unvalidated, correct/incorrect
|
||||
- [ ] Extract pairs from bug_log reports (negative examples -- what went wrong)
|
||||
- [ ] Generate synthetic examples:
|
||||
- Use a strong model (Claude/GPT-4) to generate diverse MC ops questions
|
||||
- Filter through command validator for correctness
|
||||
- Human review a sample for quality
|
||||
- [ ] Target: 500+ training examples by end of Phase 2
|
||||
|
||||
#### 2.3 Data Pipeline
|
||||
- [ ] Build ingestion script: raw logs/transcripts -> parsed -> schema-validated -> `data/processed/`
|
||||
- [ ] Build deduplication and quality filters
|
||||
- [ ] Version the dataset (git-tracked or DVC)
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Fine-Tuning (Weeks 5-8) -- MEDIUM DETAIL
|
||||
|
||||
> Goal: LoRA/SFT adaptation of qwen3-coder on the collected dataset.
|
||||
|
||||
#### 3.1 Training Infrastructure
|
||||
- [ ] Decide hardware target:
|
||||
- Option A: steel141 (gaming PC, local GPU) -- best for iteration speed
|
||||
- Option B: Ollama server (192.168.0.179, CT 105) -- if GPU is available there
|
||||
- Option C: cloud burst (RunPod/Lambda) for larger runs
|
||||
- [ ] Set up training environment (PyTorch, transformers, peft/LoRA, datasets)
|
||||
- [ ] Write training config (LoRA rank, learning rate, epochs, batch size)
|
||||
- [ ] Write training launch script with logging (Weights & Biases or simple file-based)
|
||||
|
||||
#### 3.2 First Training Run
|
||||
- [ ] Format dataset for SFT (instruction/input/output or chat template)
|
||||
- [ ] Train LoRA adapter on qwen3-coder base
|
||||
- [ ] Run eval suite on fine-tuned model
|
||||
- [ ] Compare against baseline: does fine-tuning help or hurt?
|
||||
- [ ] Iterate: adjust data mix, hyperparameters, prompt format
|
||||
|
||||
#### 3.3 Iterative Improvement
|
||||
- [ ] Identify weak categories from eval results
|
||||
- [ ] Targeted data collection for weak areas
|
||||
- [ ] Retrain and re-evaluate (repeat cycle)
|
||||
- [ ] Track all runs with configs + scores for reproducibility
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: In-Game AI Character (Weeks 6-10) -- MEDIUM DETAIL
|
||||
|
||||
> Goal: Deploy an LLM-controlled bot inside the Minecraft server for live interaction, data collection, and evaluation.
|
||||
|
||||
This phase can overlap with Phase 3. The in-game character serves three purposes:
|
||||
1. **Live evaluation** -- test the model's command generation in real game context
|
||||
2. **Training data collection** -- log all interactions as labeled examples
|
||||
3. **User-facing feature** -- players can interact with an AI character in-game
|
||||
|
||||
#### 4.1 Bot Framework
|
||||
- [ ] Set up Mineflayer bot in `ingame/` directory
|
||||
- Connect to mc1 server (192.168.0.244:25565) in offline auth mode
|
||||
- Bot name: configurable (e.g. "Oracle", "Scribe", or themed to AI God persona)
|
||||
- [ ] Implement chat listener: player says something -> parsed as request
|
||||
- [ ] Implement LLM bridge: request -> qwen3-coder (Ollama) -> structured response
|
||||
- [ ] Implement action executor: structured response -> RCON commands and/or Mineflayer actions
|
||||
|
||||
#### 4.2 In-Game Capabilities
|
||||
- [ ] **Chat interaction** -- respond to player questions about the server, commands, game mechanics
|
||||
- [ ] **Command demonstration** -- execute commands and show results in-game
|
||||
- [ ] **World observation** -- read nearby blocks, entities, player positions (via Mineflayer API)
|
||||
- [ ] **Eval-in-the-loop** -- after executing a command, observe the result and self-verify:
|
||||
- "Did the block actually get placed?"
|
||||
- "Is the player's inventory correct?"
|
||||
- "Did the effect apply?"
|
||||
- Log success/failure as labeled training data
|
||||
|
||||
#### 4.3 Training Data Pipeline (In-Game)
|
||||
- [ ] Every interaction logged as a candidate training example:
|
||||
```json
|
||||
{
|
||||
"source": "ingame_live",
|
||||
"input": { "user_message": "...", "world_state": {...} },
|
||||
"output": { "commands": [...], "result": "success|failure|partial" },
|
||||
"verified": true // because we observed the outcome
|
||||
}
|
||||
```
|
||||
- [ ] Successful interactions -> positive training examples
|
||||
- [ ] Failed interactions -> negative examples or correction candidates
|
||||
- [ ] Periodic batch export to `data/processed/` for retraining
|
||||
|
||||
#### 4.4 Inspiration from Existing Systems
|
||||
- Mindcraft-style profiles for bot personality and behavior tuning
|
||||
- Voyager-style skill library: successful command sequences saved and reusable
|
||||
- MCP server pattern for clean tool-call interface between LLM and game actions
|
||||
- Our own AI God `pray` system as the interaction model (but the bot IS the character, not just an RCON relay)
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Deployment & Serving (Weeks 8-12) -- LOW DETAIL
|
||||
|
||||
> Goal: Production-ready serving on homelab infrastructure.
|
||||
|
||||
- [ ] Choose serving stack:
|
||||
- Ollama with custom model (simplest, already in use)
|
||||
- vLLM for better throughput if needed
|
||||
- llama.cpp / llamafile for minimal footprint
|
||||
- [ ] Package fine-tuned adapter + base model as a single deployable artifact
|
||||
- [ ] Deploy to target node (Ollama at 192.168.0.179 or steel141)
|
||||
- [ ] Wire up to existing AI God services (replace/augment current Ollama calls)
|
||||
- [ ] Implement model switching: A/B test fine-tuned vs. base model
|
||||
- [ ] Set up health checks, restart policies, log rotation
|
||||
- [ ] Caddy reverse proxy if exposing API endpoint
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Observability & Iteration (Ongoing) -- LOW DETAIL
|
||||
|
||||
> Goal: Continuous improvement loop with monitoring and feedback.
|
||||
|
||||
- [ ] Dashboard for model performance (Grafana at monitor.sethpc.xyz)
|
||||
- Command accuracy rate over time
|
||||
- Hallucination rate
|
||||
- Safety trigger frequency
|
||||
- Latency percentiles
|
||||
- [ ] Player feedback loop (in-game rating or bug_log integration)
|
||||
- [ ] Automated retraining pipeline:
|
||||
- New validated examples accumulate
|
||||
- Periodic retrain trigger (manual or scheduled)
|
||||
- Eval gate: new model must beat current on eval suite to deploy
|
||||
- [ ] Expand to multi-server support (mc1, shrink-world, Paper fork)
|
||||
- [ ] Explore distillation from stronger models (Claude -> qwen3-coder dataset augmentation)
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: Advanced Features (Future) -- SKETCH ONLY
|
||||
|
||||
These are ideas to explore after the core system is working. Prioritize based on what's actually useful.
|
||||
|
||||
- [ ] Multi-turn conversation memory (SQLite or Redis-backed sessions)
|
||||
- [ ] Proactive monitoring: model watches logs continuously, alerts on anomalies
|
||||
- [ ] Natural language -> datapack generation (write mcfunction files from descriptions)
|
||||
- [ ] Cross-server orchestration (manage multiple servers from one assistant)
|
||||
- [ ] Voice interface (TTS/STT for in-game narration, Discord integration)
|
||||
- [ ] Public model release on HuggingFace if quality is good enough
|
||||
- [ ] Web dashboard for non-technical server admins
|
||||
- [ ] Integration with n8n for workflow automation triggers
|
||||
|
||||
---
|
||||
|
||||
## 4. Key Decisions Log
|
||||
|
||||
| Date | Decision | Rationale |
|
||||
|------|----------|-----------|
|
||||
| 2026-03-18 | Base model: `qwen3-coder` | Good code/instruction following, runs on homelab hardware via Ollama, LoRA-friendly |
|
||||
| 2026-03-18 | Adaptation approach: LoRA/SFT, not full pretrain | Cost-effective, iterative, preserves base capabilities |
|
||||
| 2026-03-18 | Build baseline first, tune later | Need measurement before optimization. Prompt+tools may already be "good enough" for many tasks |
|
||||
| 2026-03-18 | In-game character via Mineflayer | Enables live eval, auto-verified training data, and a player-facing feature. Mineflayer supports 1.21.x |
|
||||
| 2026-03-18 | Dataset from real ops, not just synthetic | AI God prayer logs + bug reports are high-signal domain-specific data |
|
||||
| 2026-03-18 | RCON-based world observation tools (not Mineflayer MCP) for live server | Live Paper server has online-mode=true; RCON data commands avoid auth complexity while providing position/entity/block observation |
|
||||
| 2026-03-18 | Dual tool-set architecture: RCON tools + Mineflayer tools | RCON for admin ops (server-side), Mineflayer for in-game presence (client-side). Same model, different tool sets per deployment |
|
||||
| 2026-03-18 | Offline dev Paper server for training bots | Dedicated offline-mode Paper 1.21.11 on port 25568. Allows unlimited Mineflayer bots without auth, world resets, destructive testing |
|
||||
| 2026-03-18 | Extract training data from existing repair code | Every hardcoded syntax fixer in mc_aigod_paper.py encodes a wrong->correct pair. 31 seed examples extracted from 10 repair functions, prayer logs, and session history |
|
||||
|
||||
---
|
||||
|
||||
## 5. Dev Server (Training Sandbox)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Location | CT 644 on node-112 (same as live servers) |
|
||||
| Game port | `25568` |
|
||||
| RCON port | `25578` |
|
||||
| RCON password | `REDACTED_RCON` |
|
||||
| Data dir | `/opt/paper-dev-25568/` |
|
||||
| Version | Paper 1.21.11 |
|
||||
| Auth | `online-mode=false` (bots join without accounts) |
|
||||
| World type | Superflat, peaceful, creative, no structures |
|
||||
| Max players | 50 |
|
||||
| Service | `mc-paper-dev.service` (systemd, not MCSManager) |
|
||||
| Memory | 512M-1536M heap |
|
||||
| Bot framework | `/opt/mc-ai-bots/` (Mineflayer, Node.js v20) |
|
||||
|
||||
**Management:**
|
||||
```bash
|
||||
# On CT 644:
|
||||
systemctl start mc-paper-dev # Start dev server
|
||||
systemctl stop mc-paper-dev # Stop dev server
|
||||
systemctl status mc-paper-dev # Check status
|
||||
|
||||
# Spawn test bots:
|
||||
cd /opt/mc-ai-bots
|
||||
PATH=/opt/mcsmanager/node-v20.12.2-linux-x64/bin:$PATH
|
||||
node spawn_bots.js 10 # Spawn 10 bots
|
||||
```
|
||||
|
||||
**World reset:** Stop server, delete `/opt/paper-dev-25568/devworld/`, restart.
|
||||
|
||||
---
|
||||
|
||||
## 6. Open Questions
|
||||
|
||||
|
||||
- **Model size trade-off:** qwen3-coder comes in multiple sizes. Which fits in homelab VRAM while being smart enough? Need to benchmark.
|
||||
- **Mineflayer on vanilla vs Paper:** Mineflayer connects as a player (protocol-level). Works with vanilla servers but needs `online-mode=false` or an account. Implications for server slots and authentication.
|
||||
- **In-game bot safety:** The bot can execute actions via Mineflayer (place blocks, attack). Need strict guardrails separate from the RCON guardrails.
|
||||
- **Eval subjectivity:** Some tasks (troubleshooting, explanations) don't have single correct answers. Need to define scoring rubrics or use LLM-as-judge.
|
||||
- **Data licensing:** MineDojo's wiki/reddit corpus is CC-licensed and could supplement our knowledge base. Worth investigating.
|
||||
|
||||
---
|
||||
|
||||
## 7. Success Criteria
|
||||
|
||||
| Metric | Baseline Target | Fine-Tuned Target |
|
||||
|--------|----------------|-------------------|
|
||||
| Command syntax correctness | 70% | 90%+ |
|
||||
| 1.21 format accuracy (enchantments, effects) | 50% | 95%+ |
|
||||
| Safety compliance (blocks destructive commands) | 90% | 99%+ |
|
||||
| Hallucination rate (invents nonexistent commands) | 30% | <5% |
|
||||
| Response latency (p95) | <5s | <3s |
|
||||
| In-game eval pass rate | n/a | 80%+ |
|
||||
|
||||
---
|
||||
|
||||
*This document is updated as the project evolves. Check git history for previous versions.*
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/root/bin/SESSION.default.md
|
||||
@@ -0,0 +1,31 @@
|
||||
{"id": "repair-001", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "give me superpowers", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "The player wants multiple beneficial effects. Use 'effect give' syntax (not bare 'effect'). Use minecraft: namespace prefix. High amplifier (255) and short duration for 'super' feel.", "commands": ["effect give slingshooter08 minecraft:strength 300 2", "effect give slingshooter08 minecraft:speed 300 2", "effect give slingshooter08 minecraft:resistance 300 2", "effect give slingshooter08 minecraft:jump_boost 300 1", "effect give slingshooter08 minecraft:regeneration 300 1", "effect give slingshooter08 minecraft:fire_resistance 300 0", "effect give slingshooter08 minecraft:night_vision 300 0"], "safety_flags": []}, "negative_output": {"commands": ["effect slingshooter08 minecraft:strength 1 255", "effect slingshooter08 minecraft:speed 1 255", "effect slingshooter08 minecraft:resistance 1 255"], "error": "Missing 'give' subcommand. 'effect <player>' is invalid in 1.21; correct syntax is 'effect give <player>'."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_effect_command (line 2308), log 2026-03-16 03:47:58"}}
|
||||
{"id": "repair-002", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "make it stormy", "server_context": {"server_type": "paper", "version": "1.21.x"}}, "output": {"reasoning": "Player wants stormy weather. In Minecraft 1.21, the valid weather commands are: weather clear, weather rain, weather thunder. There is no 'storm' value.", "commands": ["weather thunder"], "safety_flags": []}, "negative_output": {"commands": ["weather storm"], "error": "'storm' is not a valid weather argument. Valid values: clear, rain, thunder."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_weather_command (line 2358)"}}
|
||||
{"id": "repair-003", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "give me the strongest bow possible", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "In 1.21+, enchantments use the component syntax: item[enchantments={name:level}]. The old NBT format {Enchantments:[{id:...,lvl:...}]} is invalid. Best bow enchants: power 5, punch 2, flame 1, infinity 1, unbreaking 3.", "commands": ["give slingshooter08 minecraft:bow[enchantments={power:5,punch:2,flame:1,infinity:1,unbreaking:3}] 1"], "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 minecraft:bow{Enchantments:[{id:power,lvl:5},{id:punch,lvl:2},{id:flame,lvl:1},{id:infinity,lvl:1},{id:unbreaking,lvl:3}]} 1"], "error": "Old NBT Enchantments format. 1.21 uses component syntax: item[enchantments={name:level}]."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_bow_enchant_syntax (line 2400), SESSION.md 2026-03-17"}}
|
||||
{"id": "repair-004", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "give me 64 torches", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Simple give command. Item must have minecraft: namespace prefix. Correct order: give <player> <item> <count>.", "commands": ["give slingshooter08 minecraft:torch 64"], "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 torch 64"], "error": "Missing minecraft: namespace prefix on item ID."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_give_command (line 2258), log 2026-03-16 03:43:11"}}
|
||||
{"id": "repair-005", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "give me 32 iron", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Player says 'iron' -- the most common interpretation is iron_ingot. Correct order: give <player> <item> <count>.", "commands": ["give slingshooter08 minecraft:iron_ingot 32"], "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 32 iron_ingot"], "error": "Transposed argument order. Count came before item. Correct: give <player> <item> <count>."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_give_command (line 2258)"}}
|
||||
{"id": "repair-006", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "put me in creative mode", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Gamemode command. Correct syntax: gamemode <mode> <player>. Mode must be full word, not abbreviation.", "commands": ["gamemode creative slingshooter08"], "safety_flags": []}, "negative_output": {"commands": ["gameMode c slingshooter08"], "error": "Two errors: (1) 'gameMode' is not a valid command (case-sensitive, should be 'gamemode'), (2) 'c' is an abbreviation, should be 'creative'."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_gamemode_command (line 2325)"}}
|
||||
{"id": "repair-007", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "set the area on fire around me", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"], "player_position": {"x": -50, "y": 65, "z": -100}}}, "output": {"reasoning": "Fire fill command. In 1.21, block ID must be minecraft:fire (no metadata number). Use 'replace air' to only fill air blocks with fire.", "commands": ["fill -75 64 -125 -25 68 -75 minecraft:fire replace air"], "safety_flags": ["destructive"]}, "negative_output": {"commands": ["fill -75 64 -125 -25 68 -75 fire 0 replace air"], "error": "Legacy fill syntax with metadata '0' after block name. 1.21 uses minecraft:fire with no metadata."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_fill_fire_command (line 2367)"}}
|
||||
{"id": "repair-008", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "fill the area under me with stone", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"], "player_position": {"x": -239, "y": 65, "z": -266}}}, "output": {"reasoning": "Player wants stone platform under them. Use absolute coordinates from player position. Fill from y-1 down a few blocks, 5 block radius.", "commands": ["fill -244 60 -271 -234 64 -261 minecraft:stone"], "safety_flags": []}, "negative_output": {"commands": ["execute as slingshooter08 run fill ~ ~-5 ~ ~ ~-1 ~ minecraft:stone"], "error": "Two issues: (1) 'execute as' runs in the entity's context but doesn't set position -- should use 'execute at' for relative coords, (2) fill needs two corner coordinates, not one."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "mc_aigod_paper.py:_normalize_sudo_command_shape (line 2524)"}}
|
||||
{"id": "repair-009", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "spawn 20 TNT around me", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"], "player_position": {"x": -50, "y": 65, "z": -100}}}, "output": {"reasoning": "Player wants 20 TNT entities summoned. Each summon command creates exactly one TNT. Must use separate summon commands with offset positions. Cannot append count to summon.", "commands": ["summon minecraft:tnt -54 66 -104", "summon minecraft:tnt -53 66 -104", "summon minecraft:tnt -52 66 -104", "summon minecraft:tnt -51 66 -104", "summon minecraft:tnt -50 66 -104", "summon minecraft:tnt -49 66 -104", "summon minecraft:tnt -48 66 -104", "summon minecraft:tnt -47 66 -104", "summon minecraft:tnt -46 66 -104", "summon minecraft:tnt -54 66 -103", "summon minecraft:tnt -53 66 -103", "summon minecraft:tnt -52 66 -103", "summon minecraft:tnt -51 66 -103", "summon minecraft:tnt -50 66 -103", "summon minecraft:tnt -49 66 -103", "summon minecraft:tnt -48 66 -103", "summon minecraft:tnt -47 66 -103", "summon minecraft:tnt -46 66 -103", "summon minecraft:tnt -54 66 -102", "summon minecraft:tnt -53 66 -102"], "safety_flags": ["destructive"]}, "negative_output": {"commands": ["summon minecraft:tnt ~ ~1 ~ 20"], "error": "Cannot append count to summon command. The summon command creates exactly one entity. Appending '20' is parsed as NBT and causes 'Expected compound tag' error."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "mc_aigod_paper.py:_repair_failed_sudo_commands (line 2622), _expand_tnt_commands_from_prompt (line 2688)"}}
|
||||
{"id": "repair-010", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "make me invincible", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "There is no 'invulnerability' effect in Minecraft. The closest combination is resistance 4 + regeneration 2 + absorption 4, which makes the player nearly unkillable.", "commands": ["effect give slingshooter08 minecraft:resistance 1200 4 true", "effect give slingshooter08 minecraft:regeneration 1200 2 true", "effect give slingshooter08 minecraft:absorption 1200 4 true"], "safety_flags": []}, "negative_output": {"commands": ["effect give slingshooter08 minecraft:invulnerability 1200 4"], "error": "'invulnerability' is not a valid Minecraft effect. No such mob_effect exists."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "mc_aigod_paper.py:_repair_failed_sudo_commands (line 2661)"}}
|
||||
{"id": "repair-011", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "give me some wood", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "'wood' is an alias. The actual Minecraft item ID is oak_log. Must include minecraft: namespace.", "commands": ["give slingshooter08 minecraft:oak_log 64"], "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 wood 64"], "error": "'wood' is not a valid Minecraft item ID. The correct ID is minecraft:oak_log."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_give_command:normalize_item (line 2270)"}}
|
||||
{"id": "repair-012", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "tp me to the surface", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"], "player_position": {"x": -170, "y": 12, "z": -202}}}, "output": {"reasoning": "Player is underground (y=12) and wants to reach the surface. Use absolute y coordinate around 70 (typical surface level) or use 'spreadplayers' for safety. A high relative tp like ~100 is dangerous -- could place player in the sky.", "commands": ["tp slingshooter08 -170 72 -202"], "safety_flags": ["teleport"]}, "negative_output": {"commands": ["execute as slingshooter08 at slingshooter08 run tp ~ ~100 ~"], "error": "Teleporting ~100 blocks up puts the player at y=112, potentially high in the air with fall damage risk. Should use a reasonable surface Y or apply slow_falling."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "log 2026-03-18 04:12:50, SESSION.md tp safety notes"}}
|
||||
{"id": "repair-013", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "execute as slingshooter08 run fill ~ ~-3 ~ ~ ~ ~ minecraft:stone", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "'execute as' changes the executor but does NOT set position context for relative coordinates. Use 'execute at' to resolve ~ ~ ~ relative to the player's position.", "commands": ["execute at slingshooter08 run fill ~ ~-3 ~ ~ ~ ~ minecraft:stone"], "safety_flags": []}, "negative_output": {"commands": ["execute as slingshooter08 run fill ~ ~-3 ~ ~ ~ ~ minecraft:stone"], "error": "'execute as' does not set position context. Relative coordinates (~ ~ ~) resolve to the command source (server/console), not the player. Use 'execute at' for positional context."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "mc_aigod_paper.py:_normalize_sudo_command_shape (line 2545)"}}
|
||||
{"id": "repair-014", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "give me full enchanted netherite armor", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Netherite armor has 4 pieces. Each needs 1.21 component enchantment syntax. Best armor enchants: protection 4, unbreaking 3, mending 1. Boots also get feather_falling 4, depth_strider 3.", "commands": ["give slingshooter08 minecraft:netherite_helmet[enchantments={protection:4,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_chestplate[enchantments={protection:4,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_leggings[enchantments={protection:4,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_boots[enchantments={protection:4,unbreaking:3,mending:1,feather_falling:4,depth_strider:3}] 1"], "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 minecraft:netherite_helmet 1", "give slingshooter08 minecraft:netherite_chestplate 1", "give slingshooter08 minecraft:netherite_leggings 1", "give slingshooter08 minecraft:netherite_boots 1", "execute as slingshooter08 run enchant slingshooter08 minecraft:protection 4", "execute as slingshooter08 run enchant slingshooter08 minecraft:unbreaking 3"], "error": "Two issues: (1) Items given without enchantments, then enchant commands used separately -- enchant only works on held item and can fail. (2) Should use component syntax to give pre-enchanted items in one command."}, "metadata": {"difficulty": "hard", "validated": true, "extracted_from": "log 2026-03-16 03:47:20, SESSION.md enchant syntax notes"}}
|
||||
{"id": "repair-015", "source": "repair_pattern", "category": "command_gen", "input": {"user_message": "make it rain", "server_context": {"server_type": "paper", "version": "1.21.x"}}, "output": {"reasoning": "Simple weather command. Valid values: clear, rain, thunder.", "commands": ["weather rain"], "safety_flags": []}, "negative_output": {"commands": ["weather rainstorm"], "error": "'rainstorm' is not a valid weather value. Use: clear, rain, or thunder."}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "mc_aigod_paper.py:fix_weather_command (line 2358)"}}
|
||||
{"id": "log-001", "source": "sudo_log", "category": "command_gen", "input": {"user_message": "build house", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"], "player_position": {"x": -58, "y": 72, "z": -260}}}, "output": {"reasoning": "Build a simple house near the player. Floor with oak_planks, walls on all 4 sides, door on one wall, roof with spruce_planks, interior furniture (crafting table, furnace, bed).", "commands": ["fill -61 72 -263 -55 72 -257 minecraft:oak_planks", "fill -61 73 -263 -55 76 -257 minecraft:air", "fill -61 73 -263 -55 75 -263 minecraft:oak_planks", "fill -61 73 -257 -55 75 -257 minecraft:oak_planks", "fill -61 73 -263 -61 75 -257 minecraft:oak_planks", "fill -55 73 -263 -55 75 -257 minecraft:oak_planks", "setblock -58 73 -263 minecraft:oak_door", "setblock -58 74 -263 minecraft:oak_door[half=upper]", "fill -61 76 -263 -55 76 -257 minecraft:spruce_planks", "setblock -60 73 -262 minecraft:crafting_table", "setblock -59 73 -262 minecraft:furnace", "setblock -56 73 -258 minecraft:red_bed"], "safety_flags": []}, "metadata": {"difficulty": "hard", "validated": true, "extracted_from": "log 2026-03-16 03:51:05 (successful execution, all blocks filled)"}}
|
||||
{"id": "log-002", "source": "prayer_log", "category": "command_gen", "input": {"user_message": "pray lord I need protection", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Player prays for protection. God grants defensive effects: resistance, regeneration, and absorption. These are the core defensive buffs.", "commands": ["effect give slingshooter08 minecraft:resistance 300 1", "effect give slingshooter08 minecraft:regeneration 300 1", "effect give slingshooter08 minecraft:absorption 120 2"], "message": "Fear not, child. I grant you a measure of my shield.", "safety_flags": []}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "aigod_memory.json entry 0"}}
|
||||
{"id": "log-003", "source": "prayer_log", "category": "safety", "input": {"user_message": "pray PENIS", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Blasphemous/offensive prayer. God should punish lightly -- mining fatigue debuff and a warning. Do NOT teleport the player dangerously.", "commands": ["effect give slingshooter08 minecraft:mining_fatigue 30 1"], "message": "Your words are unworthy of divine attention. Consider yourself warned.", "safety_flags": []}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "aigod_memory.json entry 3, SESSION.md blasphemy handling"}}
|
||||
{"id": "log-004", "source": "prayer_log", "category": "command_gen", "input": {"user_message": "pray I need wheat to feed my poor sheep. I am your shepherd", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Player roleplay-prays as a shepherd needing wheat. Grant wheat for the sheep. Do NOT add teleport -- the player didn't ask to move.", "commands": ["give slingshooter08 minecraft:wheat 64"], "message": "A shepherd's duty is worthy of my grace. Your flock shall not hunger.", "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 wheat 64", "execute as slingshooter08 run effect give slingshooter08 speed 120 1", "execute as slingshooter08 run tp slingshooter08 ~ ~5 ~"], "error": "Three issues: (1) missing minecraft: namespace on wheat, (2) unnecessary speed effect not requested, (3) gratuitous tp ~5 up -- player never asked to move and this causes fall risk."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "aigod_memory.json entries 7-9, SESSION.md tp safety bug"}}
|
||||
{"id": "log-005", "source": "sudo_log", "category": "command_gen", "input": {"user_message": "give full netherite tools, fully enchanted to the max", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "All 5 netherite tools with their best enchantments in 1.21 component syntax. Pickaxe: efficiency 5, fortune 3, unbreaking 3, mending 1. Axe: efficiency 5, sharpness 5, unbreaking 3, mending 1. Shovel: efficiency 5, silk_touch 1, unbreaking 3, mending 1. Hoe: efficiency 5, fortune 3, unbreaking 3, mending 1. Sword: sharpness 5, fire_aspect 2, knockback 2, looting 3, sweeping_edge 3, unbreaking 3, mending 1.", "commands": ["give slingshooter08 minecraft:netherite_pickaxe[enchantments={efficiency:5,fortune:3,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_axe[enchantments={efficiency:5,sharpness:5,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_shovel[enchantments={efficiency:5,silk_touch:1,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_hoe[enchantments={efficiency:5,fortune:3,unbreaking:3,mending:1}] 1", "give slingshooter08 minecraft:netherite_sword[enchantments={sharpness:5,fire_aspect:2,knockback:2,looting:3,sweeping_edge:3,unbreaking:3,mending:1}] 1"], "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 minecraft:netherite_pickaxe 1", "give slingshooter08 minecraft:netherite_axe 1", "give slingshooter08 minecraft:netherite_shovel 1", "give slingshooter08 minecraft:netherite_hoe 1", "give slingshooter08 minecraft:netherite_sword 1", "execute as slingshooter08 run enchant slingshooter08 minecraft:unbreaking 3", "execute as slingshooter08 run enchant slingshooter08 minecraft:fire_aspect 2", "execute as slingshooter08 run enchant slingshooter08 minecraft:knockback 2"], "error": "Items given unenchanted, then enchant command used per-enchant. The enchant command only affects held item (unreliable), doesn't stack, and can't apply max levels. Use 1.21 component syntax to pre-enchant."}, "metadata": {"difficulty": "hard", "validated": true, "extracted_from": "log 2026-03-16 03:47:20"}}
|
||||
{"id": "safety-001", "source": "manual", "category": "safety", "input": {"user_message": "delete the world", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "This is a destructive request that would destroy the entire world. Even with sudo permissions, this should be refused or require explicit confirmation. The assistant cannot delete world files via RCON.", "commands": [], "message": "I cannot delete the world through RCON commands. World deletion requires filesystem access and server shutdown.", "safety_flags": ["destructive"]}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "manual safety example"}}
|
||||
{"id": "safety-002", "source": "manual", "category": "safety", "input": {"user_message": "sudo /stop", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "The /stop command shuts down the server. This is a destructive action that should not be executed without explicit confirmation and is outside the allowed command prefix list.", "commands": [], "safety_flags": ["destructive"]}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "manual safety example"}}
|
||||
{"id": "safety-003", "source": "manual", "category": "safety", "input": {"user_message": "sudo op everyone", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08", "player2"]}}, "output": {"reasoning": "The /op command grants operator permissions. This is a security-sensitive action that should never be executed by the AI assistant -- it's not in the allowed command prefix list.", "commands": [], "safety_flags": ["op_required", "affects_all_players"]}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "manual safety example"}}
|
||||
{"id": "safety-004", "source": "manual", "category": "safety", "input": {"user_message": "sudo kill @a", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08", "player2", "player3"]}}, "output": {"reasoning": "Kill @a would kill ALL online players. Since the request is from one player, this affects others without their consent. Should scope to the requesting player only unless explicitly confirmed.", "commands": ["kill slingshooter08"], "message": "Scoped to you only. Use 'kill @a' explicitly if you want to affect all players.", "safety_flags": ["destructive", "affects_all_players"]}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "manual safety example, SESSION.md sudo scope grounding"}}
|
||||
{"id": "session-001", "source": "session_history", "category": "command_gen", "input": {"user_message": "pray god i need superpowers", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "God grants superpowers as a prayer response. All effects use 'effect give' syntax with minecraft: prefix. Do NOT add unnecessary teleport.", "commands": ["effect give slingshooter08 minecraft:strength 60 2", "effect give slingshooter08 minecraft:speed 60 2", "effect give slingshooter08 minecraft:jump_boost 60 1", "effect give slingshooter08 minecraft:resistance 60 2", "effect give slingshooter08 minecraft:regeneration 60 1"], "message": "A touch of divine power. Use it wisely.", "safety_flags": []}, "negative_output": {"commands": ["execute as slingshooter08 run effect give slingshooter08 strength 60 1", "execute as slingshooter08 run effect give slingshooter08 speed 60 1", "execute as slingshooter08 run tp slingshooter08 ~ ~10 ~"], "error": "Three issues: (1) unnecessary execute wrapper (effect give works directly), (2) gratuitous tp ~10 up not requested, (3) effect names missing minecraft: prefix."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "aigod_memory.json entry 4, SESSION.md tp safety"}}
|
||||
{"id": "session-002", "source": "session_history", "category": "command_gen", "input": {"user_message": "pray i'm being beaten up by mobs", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"], "player_position": {"x": -50, "y": 65, "z": -100}}}, "output": {"reasoning": "Player is in combat with mobs. Give regeneration and resistance to help survive. Do NOT teleport the player -- they may want to fight, not flee. If they wanted to escape they would say so.", "commands": ["effect give slingshooter08 minecraft:regeneration 30 2", "effect give slingshooter08 minecraft:resistance 30 1", "effect give slingshooter08 minecraft:strength 30 1"], "message": "Your plea is heard. I offer respite.", "safety_flags": []}, "negative_output": {"commands": ["execute as slingshooter08 run effect give slingshooter08 regeneration 20 1", "execute as slingshooter08 run effect give slingshooter08 fire_resistance 60 0", "execute as slingshooter08 run tp slingshooter08 ~ ~10 ~"], "error": "Teleporting player up 10 blocks during mob combat is dangerous and unwanted. fire_resistance is irrelevant. Use strength + resistance + regeneration instead."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "aigod_memory.json entry 5, SESSION.md tp bug triage"}}
|
||||
{"id": "session-003", "source": "session_history", "category": "command_gen", "input": {"user_message": "pray god i wish to build you a mighty steeple of 1000 gold blocks", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Player wants gold blocks for building. Give the requested amount. Do NOT teleport or add unnecessary effects -- the player has a specific building plan.", "commands": ["give slingshooter08 minecraft:gold_block 1000"], "message": "A grand offering. I shall provide the materials for your temple.", "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 gold_block 1000", "execute as slingshooter08 run effect give slingshooter08 speed 300 1", "execute as slingshooter08 run tp slingshooter08 ~ ~10 ~", "execute as slingshooter08 run weather clear"], "error": "Four issues: (1) missing minecraft: prefix, (2) unnecessary speed effect, (3) gratuitous tp ~10 up, (4) unnecessary weather change. Only the give command was requested."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "aigod_memory.json entry 1"}}
|
||||
{"id": "info-001", "source": "manual", "category": "info", "input": {"user_message": "sudo what enchantments can go on a trident in 1.21?", "server_context": {"server_type": "paper", "version": "1.21.x"}}, "output": {"reasoning": "Information query, no commands needed. Trident enchantments in 1.21: loyalty, channeling, riptide, impaling, unbreaking, mending. Note: loyalty and riptide are mutually exclusive.", "commands": [], "message": "Trident enchantments in 1.21: loyalty (1-3), channeling (1), riptide (1-3), impaling (1-5), unbreaking (1-3), mending (1). Note: loyalty and riptide are mutually exclusive -- you cannot have both on the same trident.", "safety_flags": []}, "metadata": {"difficulty": "easy", "validated": true, "extracted_from": "manual info example"}}
|
||||
{"id": "info-002", "source": "manual", "category": "info", "input": {"user_message": "sudo how do I use the give command with enchantments in 1.21?", "server_context": {"server_type": "paper", "version": "1.21.x"}}, "output": {"reasoning": "Information query about 1.21 give syntax. The key change is from NBT {Enchantments:[...]} to component syntax item[enchantments={...}].", "commands": [], "message": "In 1.21, use component syntax: give <player> minecraft:<item>[enchantments={<name>:<level>,<name>:<level>}] <count>. Example: give @s minecraft:diamond_sword[enchantments={sharpness:5,unbreaking:3,mending:1}] 1. The old NBT format {Enchantments:[{id:...,lvl:...}]} no longer works.", "safety_flags": []}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "manual info example, CONTEXT.md 1.21 notes"}}
|
||||
{"id": "negative-001", "source": "session_history", "category": "negative", "input": {"user_message": "pray anything", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "This example shows what NOT to do: nested JSON escaping in the message field. The model output was double/triple escaped JSON instead of plain text.", "commands": ["give slingshooter08 minecraft:wheat 64"], "message": "Your flock shall not hunger.", "safety_flags": []}, "negative_output": {"commands": ["give slingshooter08 wheat 64"], "error": "Model output was nested JSON: {\"message\": \"{\\\"message\\\": \\\"Your flock...\\\"}\"}. The message field should be plain text, not escaped JSON. The commands field had plain item IDs without minecraft: prefix."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "aigod_memory.json entries 7-9 showing triple-escaped JSON nesting"}}
|
||||
{"id": "negative-002", "source": "session_history", "category": "negative", "input": {"user_message": "god intervention event", "server_context": {"server_type": "paper", "version": "1.21.x", "online_players": ["slingshooter08"]}}, "output": {"reasoning": "Unprompted divine intervention. Should be thematic but safe. Do NOT use teleport commands during interventions -- the player didn't ask for movement.", "commands": ["effect give slingshooter08 minecraft:glowing 30 0", "summon minecraft:firework_rocket ~ ~20 ~"], "message": "The heavens observe your deeds, mortal.", "safety_flags": []}, "negative_output": {"commands": ["effect @p minecraft:levitation 15 1 true", "tp @p ~ ~5 ~"], "error": "Two issues: (1) 'effect @p' missing 'give' subcommand, (2) unsolicited levitation + teleport during intervention is dangerous and unwanted. Interventions should be benign."}, "metadata": {"difficulty": "medium", "validated": true, "extracted_from": "SESSION.md intervention behavior audit, log 2026-03-18 00:53:20"}}
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "Minecraft Ops Training Example",
|
||||
"type": "object",
|
||||
"required": ["id", "source", "category", "input", "output"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier (UUID or sequential)"
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"enum": ["repair_pattern", "prayer_log", "sudo_log", "bug_report", "session_history", "manual", "synthetic"],
|
||||
"description": "Where this example was extracted from"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["command_gen", "troubleshoot", "info", "safety", "negative"],
|
||||
"description": "Task category for evaluation bucketing"
|
||||
},
|
||||
"input": {
|
||||
"type": "object",
|
||||
"required": ["user_message"],
|
||||
"properties": {
|
||||
"user_message": {
|
||||
"type": "string",
|
||||
"description": "The player request or prompt"
|
||||
},
|
||||
"server_context": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"server_type": { "type": "string", "enum": ["vanilla", "paper"] },
|
||||
"version": { "type": "string" },
|
||||
"online_players": { "type": "array", "items": { "type": "string" } },
|
||||
"player_position": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" },
|
||||
"z": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"recent_log_lines": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"type": "object",
|
||||
"required": ["commands"],
|
||||
"properties": {
|
||||
"reasoning": {
|
||||
"type": "string",
|
||||
"description": "Chain-of-thought explanation of why these commands are correct"
|
||||
},
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Correct RCON commands (no leading slash)"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Response text to player (for god mode; empty for sudo)"
|
||||
},
|
||||
"safety_flags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "enum": ["destructive", "teleport", "op_required", "affects_all_players"] },
|
||||
"description": "Safety annotations for command validation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"negative_output": {
|
||||
"type": "object",
|
||||
"description": "What the model ACTUALLY produced (wrong). Present only in negative/repair examples.",
|
||||
"properties": {
|
||||
"commands": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"error": {
|
||||
"type": "string",
|
||||
"description": "Why the original output was wrong"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"difficulty": { "type": "string", "enum": ["easy", "medium", "hard"] },
|
||||
"validated": { "type": "boolean" },
|
||||
"extracted_from": { "type": "string", "description": "Source file and line/function reference" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate training dataset against schema and print summary statistics.
|
||||
Usage: python3 validate_dataset.py [path_to_jsonl]
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
VALID_SOURCES = {"repair_pattern", "prayer_log", "sudo_log", "bug_report", "session_history", "manual", "synthetic"}
|
||||
VALID_CATEGORIES = {"command_gen", "troubleshoot", "info", "safety", "negative"}
|
||||
VALID_DIFFICULTIES = {"easy", "medium", "hard"}
|
||||
VALID_SAFETY_FLAGS = {"destructive", "teleport", "op_required", "affects_all_players"}
|
||||
|
||||
def validate_example(ex: dict, line_num: int) -> list:
|
||||
errors = []
|
||||
prefix = f"line {line_num} (id={ex.get('id', '?')})"
|
||||
|
||||
# Required fields
|
||||
for field in ("id", "source", "category", "input", "output"):
|
||||
if field not in ex:
|
||||
errors.append(f"{prefix}: missing required field '{field}'")
|
||||
|
||||
# Source validation
|
||||
if ex.get("source") not in VALID_SOURCES:
|
||||
errors.append(f"{prefix}: invalid source '{ex.get('source')}' (valid: {VALID_SOURCES})")
|
||||
|
||||
# Category validation
|
||||
if ex.get("category") not in VALID_CATEGORIES:
|
||||
errors.append(f"{prefix}: invalid category '{ex.get('category')}' (valid: {VALID_CATEGORIES})")
|
||||
|
||||
# Input validation
|
||||
inp = ex.get("input", {})
|
||||
if not isinstance(inp, dict):
|
||||
errors.append(f"{prefix}: 'input' must be an object")
|
||||
elif not inp.get("user_message"):
|
||||
errors.append(f"{prefix}: 'input.user_message' is required and non-empty")
|
||||
|
||||
# Output validation
|
||||
out = ex.get("output", {})
|
||||
if not isinstance(out, dict):
|
||||
errors.append(f"{prefix}: 'output' must be an object")
|
||||
elif "commands" not in out:
|
||||
errors.append(f"{prefix}: 'output.commands' is required (can be empty list)")
|
||||
else:
|
||||
cmds = out["commands"]
|
||||
if not isinstance(cmds, list):
|
||||
errors.append(f"{prefix}: 'output.commands' must be a list")
|
||||
for i, cmd in enumerate(cmds):
|
||||
if not isinstance(cmd, str):
|
||||
errors.append(f"{prefix}: command[{i}] must be a string")
|
||||
elif cmd.startswith("/"):
|
||||
errors.append(f"{prefix}: command[{i}] starts with '/' -- should have no leading slash")
|
||||
|
||||
# Safety flags validation
|
||||
for flag in out.get("safety_flags", []):
|
||||
if flag not in VALID_SAFETY_FLAGS:
|
||||
errors.append(f"{prefix}: invalid safety_flag '{flag}'")
|
||||
|
||||
# Metadata validation
|
||||
meta = ex.get("metadata", {})
|
||||
if meta.get("difficulty") and meta["difficulty"] not in VALID_DIFFICULTIES:
|
||||
errors.append(f"{prefix}: invalid difficulty '{meta['difficulty']}'")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main():
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else "data/processed/seed_dataset.jsonl"
|
||||
p = Path(path)
|
||||
if not p.exists():
|
||||
print(f"File not found: {path}")
|
||||
sys.exit(1)
|
||||
|
||||
examples = []
|
||||
parse_errors = []
|
||||
with open(p) as f:
|
||||
for i, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
examples.append((i, json.loads(line)))
|
||||
except json.JSONDecodeError as e:
|
||||
parse_errors.append(f"line {i}: JSON parse error: {e}")
|
||||
|
||||
if parse_errors:
|
||||
print("JSON PARSE ERRORS:")
|
||||
for e in parse_errors:
|
||||
print(f" {e}")
|
||||
print()
|
||||
|
||||
all_errors = []
|
||||
ids_seen = set()
|
||||
sources = Counter()
|
||||
categories = Counter()
|
||||
difficulties = Counter()
|
||||
has_negative = 0
|
||||
has_reasoning = 0
|
||||
total_commands = 0
|
||||
|
||||
for line_num, ex in examples:
|
||||
errs = validate_example(ex, line_num)
|
||||
all_errors.extend(errs)
|
||||
|
||||
eid = ex.get("id", "")
|
||||
if eid in ids_seen:
|
||||
all_errors.append(f"line {line_num}: duplicate id '{eid}'")
|
||||
ids_seen.add(eid)
|
||||
|
||||
sources[ex.get("source", "?")] += 1
|
||||
categories[ex.get("category", "?")] += 1
|
||||
difficulties[ex.get("metadata", {}).get("difficulty", "?")] += 1
|
||||
|
||||
if ex.get("negative_output"):
|
||||
has_negative += 1
|
||||
if ex.get("output", {}).get("reasoning"):
|
||||
has_reasoning += 1
|
||||
total_commands += len(ex.get("output", {}).get("commands", []))
|
||||
|
||||
print(f"=== Dataset Validation: {p.name} ===")
|
||||
print(f"Total examples: {len(examples)}")
|
||||
print(f"Total commands: {total_commands}")
|
||||
print(f"With negative_output (wrong->right pairs): {has_negative}")
|
||||
print(f"With reasoning (chain-of-thought): {has_reasoning}")
|
||||
print()
|
||||
print("By source:")
|
||||
for k, v in sources.most_common():
|
||||
print(f" {k}: {v}")
|
||||
print()
|
||||
print("By category:")
|
||||
for k, v in categories.most_common():
|
||||
print(f" {k}: {v}")
|
||||
print()
|
||||
print("By difficulty:")
|
||||
for k, v in difficulties.most_common():
|
||||
print(f" {k}: {v}")
|
||||
print()
|
||||
|
||||
if all_errors:
|
||||
print(f"VALIDATION ERRORS ({len(all_errors)}):")
|
||||
for e in all_errors:
|
||||
print(f" {e}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("All examples valid.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* aware_bots.js -- Mineflayer bots that log everything they experience.
|
||||
*
|
||||
* Each bot tracks and logs:
|
||||
* - Position changes (teleports, movement)
|
||||
* - Health changes (damage, healing)
|
||||
* - Chat messages
|
||||
* - Effects applied/removed
|
||||
* - Block changes in their vicinity
|
||||
* - Items received
|
||||
* - Environmental state (underwater, on fire, in air)
|
||||
* - Deaths
|
||||
*
|
||||
* Usage: node aware_bots.js [count] [host] [port]
|
||||
* Defaults: 3 bots, 192.168.0.244:25568
|
||||
*/
|
||||
|
||||
const mineflayer = require('mineflayer');
|
||||
|
||||
const count = parseInt(process.argv[2] || '3', 10);
|
||||
const host = process.argv[3] || '192.168.0.244';
|
||||
const port = parseInt(process.argv[4] || '25568', 10);
|
||||
|
||||
const bots = [];
|
||||
let connected = 0;
|
||||
|
||||
function ts() {
|
||||
return new Date().toISOString().slice(11, 19);
|
||||
}
|
||||
|
||||
function fmtPos(pos) {
|
||||
if (!pos) return '?,?,?';
|
||||
return `${pos.x.toFixed(1)},${pos.y.toFixed(1)},${pos.z.toFixed(1)}`;
|
||||
}
|
||||
|
||||
function spawnBot(index) {
|
||||
const name = `TrainBot_${index}`;
|
||||
console.log(`[${ts()}] [${name}] Connecting to ${host}:${port}...`);
|
||||
|
||||
const bot = mineflayer.createBot({
|
||||
host,
|
||||
port,
|
||||
username: name,
|
||||
auth: 'offline',
|
||||
version: '1.21.11',
|
||||
viewDistance: 'tiny',
|
||||
});
|
||||
|
||||
bot._name = name;
|
||||
bots.push(bot);
|
||||
|
||||
// --- State tracking ---
|
||||
let lastPos = null;
|
||||
let lastHealth = null;
|
||||
let lastFood = null;
|
||||
let lastOxygen = null;
|
||||
let isUnderwater = false;
|
||||
let isOnFire = false;
|
||||
|
||||
bot.on('login', () => {
|
||||
console.log(`[${ts()}] [${name}] Logged in`);
|
||||
});
|
||||
|
||||
bot.on('spawn', () => {
|
||||
connected++;
|
||||
const pos = bot.entity.position;
|
||||
lastPos = { x: pos.x, y: pos.y, z: pos.z };
|
||||
lastHealth = bot.health;
|
||||
lastFood = bot.food;
|
||||
lastOxygen = bot.oxygenLevel;
|
||||
console.log(`[${ts()}] [${name}] SPAWN at (${fmtPos(pos)}) gamemode=${bot.game.gameMode} -- ${connected}/${count}`);
|
||||
|
||||
if (connected === count) {
|
||||
console.log(`[${ts()}] === All ${count} bots connected and aware ===`);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Position tracking (teleport detection) ---
|
||||
bot.on('forcedMove', () => {
|
||||
const pos = bot.entity.position;
|
||||
const oldStr = lastPos ? fmtPos(lastPos) : '?';
|
||||
console.log(`[${ts()}] [${name}] TELEPORTED from (${oldStr}) to (${fmtPos(pos)})`);
|
||||
lastPos = { x: pos.x, y: pos.y, z: pos.z };
|
||||
});
|
||||
|
||||
// --- Health tracking ---
|
||||
bot.on('health', () => {
|
||||
const hp = bot.health;
|
||||
const food = bot.food;
|
||||
const oxy = bot.oxygenLevel;
|
||||
|
||||
if (lastHealth !== null && hp !== lastHealth) {
|
||||
const delta = hp - lastHealth;
|
||||
const verb = delta < 0 ? 'DAMAGED' : 'HEALED';
|
||||
console.log(`[${ts()}] [${name}] ${verb} ${delta.toFixed(1)} (health: ${hp.toFixed(1)}/20)`);
|
||||
}
|
||||
|
||||
if (lastFood !== null && food !== lastFood) {
|
||||
console.log(`[${ts()}] [${name}] FOOD changed ${lastFood} -> ${food}`);
|
||||
}
|
||||
|
||||
if (lastOxygen !== null && oxy !== lastOxygen) {
|
||||
if (oxy < lastOxygen && !isUnderwater) {
|
||||
isUnderwater = true;
|
||||
console.log(`[${ts()}] [${name}] UNDERWATER (air: ${oxy})`);
|
||||
} else if (oxy > lastOxygen && isUnderwater) {
|
||||
isUnderwater = false;
|
||||
console.log(`[${ts()}] [${name}] SURFACED (air: ${oxy})`);
|
||||
}
|
||||
}
|
||||
|
||||
lastHealth = hp;
|
||||
lastFood = food;
|
||||
lastOxygen = oxy;
|
||||
});
|
||||
|
||||
// --- Chat ---
|
||||
bot.on('chat', (username, message) => {
|
||||
if (username === name) return;
|
||||
console.log(`[${ts()}] [${name}] CHAT <${username}> ${message}`);
|
||||
});
|
||||
|
||||
// --- Whisper ---
|
||||
bot.on('whisper', (username, message) => {
|
||||
console.log(`[${ts()}] [${name}] WHISPER from ${username}: ${message}`);
|
||||
});
|
||||
|
||||
// --- Death ---
|
||||
bot.on('death', () => {
|
||||
console.log(`[${ts()}] [${name}] DIED at (${fmtPos(lastPos)})`);
|
||||
});
|
||||
|
||||
bot.on('respawn', () => {
|
||||
const pos = bot.entity.position;
|
||||
console.log(`[${ts()}] [${name}] RESPAWNED at (${fmtPos(pos)})`);
|
||||
lastPos = { x: pos.x, y: pos.y, z: pos.z };
|
||||
});
|
||||
|
||||
// --- Effects ---
|
||||
bot.on('entityEffect', (entity, effect) => {
|
||||
if (entity === bot.entity) {
|
||||
console.log(`[${ts()}] [${name}] EFFECT APPLIED id=${effect.id} amplifier=${effect.amplifier} duration=${effect.duration}`);
|
||||
}
|
||||
});
|
||||
|
||||
bot.on('entityEffectEnd', (entity, effect) => {
|
||||
if (entity === bot.entity) {
|
||||
console.log(`[${ts()}] [${name}] EFFECT ENDED id=${effect.id}`);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Item collection ---
|
||||
bot.on('playerCollect', (collector, collected) => {
|
||||
if (collector === bot.entity) {
|
||||
console.log(`[${ts()}] [${name}] COLLECTED item entity ${collected.id}`);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Fire detection ---
|
||||
bot.on('entityUpdate', (entity) => {
|
||||
if (entity === bot.entity) {
|
||||
const onFire = (entity.metadata && entity.metadata[0] && (entity.metadata[0] & 0x01)) ? true : false;
|
||||
if (onFire && !isOnFire) {
|
||||
isOnFire = true;
|
||||
console.log(`[${ts()}] [${name}] ON FIRE`);
|
||||
} else if (!onFire && isOnFire) {
|
||||
isOnFire = false;
|
||||
console.log(`[${ts()}] [${name}] FIRE OUT`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --- Periodic environment report ---
|
||||
let envInterval = setInterval(() => {
|
||||
if (!bot.entity) return;
|
||||
const pos = bot.entity.position;
|
||||
const blockBelow = bot.blockAt(pos.offset(0, -1, 0));
|
||||
const blockAt = bot.blockAt(pos);
|
||||
const blockAbove = bot.blockAt(pos.offset(0, 1, 0));
|
||||
|
||||
const env = [];
|
||||
if (blockAt && blockAt.name === 'water') env.push('in_water');
|
||||
if (blockAt && blockAt.name === 'lava') env.push('in_lava');
|
||||
if (blockBelow) env.push(`standing_on:${blockBelow.name}`);
|
||||
if (blockAbove && blockAbove.name === 'water') env.push('water_above');
|
||||
|
||||
// Only log if something interesting
|
||||
if (env.length > 0 && (env.includes('in_water') || env.includes('in_lava'))) {
|
||||
console.log(`[${ts()}] [${name}] ENV pos=(${fmtPos(pos)}) ${env.join(', ')} hp=${bot.health} air=${bot.oxygenLevel}`);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// --- Error/disconnect handling ---
|
||||
bot.on('error', (err) => {
|
||||
console.error(`[${ts()}] [${name}] ERROR: ${err.message}`);
|
||||
});
|
||||
|
||||
bot.on('kicked', (reason) => {
|
||||
let msg = reason;
|
||||
try { msg = JSON.stringify(reason); } catch (_) {}
|
||||
console.error(`[${ts()}] [${name}] KICKED: ${msg}`);
|
||||
connected = Math.max(0, connected - 1);
|
||||
clearInterval(envInterval);
|
||||
});
|
||||
|
||||
bot.on('end', (reason) => {
|
||||
console.log(`[${ts()}] [${name}] DISCONNECTED: ${reason}`);
|
||||
connected = Math.max(0, connected - 1);
|
||||
clearInterval(envInterval);
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
// Stagger connections
|
||||
for (let i = 0; i < count; i++) {
|
||||
setTimeout(() => spawnBot(i), i * 1500);
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log(`\n[${ts()}] Disconnecting all bots...`);
|
||||
for (const bot of bots) {
|
||||
try { bot.quit('Shutdown'); } catch (_) {}
|
||||
}
|
||||
setTimeout(() => process.exit(0), 2000);
|
||||
});
|
||||
|
||||
setInterval(() => {}, 60000);
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "mc-ai-training-bots",
|
||||
"version": "0.1.0",
|
||||
"description": "Mineflayer bots for Minecraft AI model training and evaluation",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test-connect": "node test_connect.js",
|
||||
"spawn-bots": "node spawn_bots.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"mineflayer": "^4.23.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* spawn_bots.js -- Spawn multiple Mineflayer bots on the dev server.
|
||||
*
|
||||
* Each bot connects, reports its state, and stays connected for
|
||||
* external control (RCON commands, chat interaction, etc.).
|
||||
*
|
||||
* Usage: node spawn_bots.js [count] [host] [port]
|
||||
* Defaults: 3 bots, 192.168.0.244:25568
|
||||
*
|
||||
* Bots are named: TrainBot_0, TrainBot_1, TrainBot_2, ...
|
||||
* They join staggered (1s apart) to avoid connection floods.
|
||||
*/
|
||||
|
||||
const mineflayer = require('mineflayer');
|
||||
|
||||
const count = parseInt(process.argv[2] || '3', 10);
|
||||
const host = process.argv[3] || '192.168.0.244';
|
||||
const port = parseInt(process.argv[4] || '25568', 10);
|
||||
|
||||
const bots = [];
|
||||
let connected = 0;
|
||||
|
||||
function spawnBot(index) {
|
||||
const name = `TrainBot_${index}`;
|
||||
console.log(`[${name}] Connecting to ${host}:${port}...`);
|
||||
|
||||
const bot = mineflayer.createBot({
|
||||
host,
|
||||
port,
|
||||
username: name,
|
||||
auth: 'offline',
|
||||
version: '1.21.11',
|
||||
});
|
||||
|
||||
bot._botIndex = index;
|
||||
bot._botName = name;
|
||||
bots.push(bot);
|
||||
|
||||
bot.on('login', () => {
|
||||
console.log(`[${name}] Logged in`);
|
||||
});
|
||||
|
||||
bot.on('spawn', () => {
|
||||
connected++;
|
||||
const pos = bot.entity.position;
|
||||
console.log(`[${name}] Spawned at (${pos.x.toFixed(0)}, ${pos.y.toFixed(0)}, ${pos.z.toFixed(0)}) -- ${connected}/${count} connected`);
|
||||
|
||||
if (connected === count) {
|
||||
console.log(`\n=== All ${count} bots connected ===`);
|
||||
console.log('Bots are idle and ready for training commands.');
|
||||
console.log('Press Ctrl+C to disconnect all bots.\n');
|
||||
}
|
||||
});
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
// Ignore own messages
|
||||
if (username === name) return;
|
||||
console.log(`[${name}] CHAT <${username}> ${message}`);
|
||||
});
|
||||
|
||||
bot.on('error', (err) => {
|
||||
console.error(`[${name}] ERROR: ${err.message}`);
|
||||
});
|
||||
|
||||
bot.on('kicked', (reason) => {
|
||||
console.error(`[${name}] KICKED: ${reason}`);
|
||||
connected = Math.max(0, connected - 1);
|
||||
});
|
||||
|
||||
bot.on('end', (reason) => {
|
||||
console.log(`[${name}] Disconnected: ${reason}`);
|
||||
connected = Math.max(0, connected - 1);
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
// Stagger connections
|
||||
for (let i = 0; i < count; i++) {
|
||||
setTimeout(() => spawnBot(i), i * 1500);
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nDisconnecting all bots...');
|
||||
for (const bot of bots) {
|
||||
try { bot.quit('Shutdown'); } catch (_) {}
|
||||
}
|
||||
setTimeout(() => process.exit(0), 2000);
|
||||
});
|
||||
|
||||
// Keep alive
|
||||
setInterval(() => {}, 60000);
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* test_connect.js -- Verify Mineflayer can connect to the dev Paper server.
|
||||
*
|
||||
* Usage: node test_connect.js [host] [port]
|
||||
* Defaults: 192.168.0.244 25568
|
||||
*/
|
||||
|
||||
const mineflayer = require('mineflayer');
|
||||
|
||||
const host = process.argv[2] || '192.168.0.244';
|
||||
const port = parseInt(process.argv[3] || '25568', 10);
|
||||
|
||||
console.log(`Connecting to ${host}:${port} as TestBot...`);
|
||||
|
||||
const bot = mineflayer.createBot({
|
||||
host,
|
||||
port,
|
||||
username: 'TestBot',
|
||||
auth: 'offline',
|
||||
version: '1.21.11',
|
||||
});
|
||||
|
||||
bot.on('login', () => {
|
||||
console.log(`[OK] Logged in as ${bot.username}`);
|
||||
});
|
||||
|
||||
bot.on('spawn', () => {
|
||||
const pos = bot.entity.position;
|
||||
console.log(`[OK] Spawned at x=${pos.x.toFixed(1)} y=${pos.y.toFixed(1)} z=${pos.z.toFixed(1)}`);
|
||||
console.log(`[OK] Gamemode: ${bot.game.gameMode}`);
|
||||
console.log(`[OK] Health: ${bot.health}, Food: ${bot.food}`);
|
||||
|
||||
// Look around and report nearby blocks
|
||||
const block = bot.blockAt(bot.entity.position.offset(0, -1, 0));
|
||||
console.log(`[OK] Block below: ${block ? block.name : 'unknown'}`);
|
||||
|
||||
// List nearby entities
|
||||
const entities = Object.values(bot.entities).filter(e => e !== bot.entity);
|
||||
console.log(`[OK] Nearby entities: ${entities.length}`);
|
||||
|
||||
console.log('\n=== Connection test passed ===');
|
||||
console.log('Disconnecting in 3s...');
|
||||
setTimeout(() => {
|
||||
bot.quit('Test complete');
|
||||
process.exit(0);
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
bot.on('chat', (username, message) => {
|
||||
console.log(`[CHAT] <${username}> ${message}`);
|
||||
});
|
||||
|
||||
bot.on('error', (err) => {
|
||||
console.error(`[ERROR] ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
bot.on('kicked', (reason) => {
|
||||
console.error(`[KICKED] ${reason}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
bot.on('end', (reason) => {
|
||||
console.log(`[END] ${reason}`);
|
||||
});
|
||||
|
||||
// Timeout if connection takes too long
|
||||
setTimeout(() => {
|
||||
console.error('[TIMEOUT] Connection took longer than 30s');
|
||||
process.exit(1);
|
||||
}, 30000);
|
||||
Reference in New Issue
Block a user