Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# CurseForge API Key (required for automatic modpack download)
# Get your API key from: https://console.curseforge.com/?/api-keys
CF_API_KEY=$2a$10$...

# --- Backups (DC-126) ---
# Local placeholder; final destination will be Hetzner Storage Box or B2.
# Bind-mount path on the host where mc-backup writes snapshots.
BACKUP_DEST=./server/backups
# How often to snapshot. itzg/mc-backup parses Go-duration strings.
BACKUP_INTERVAL=24h
# RCON credentials for save flush before snapshot. Defaults match itzg image.
RCON_PASSWORD=daemoncraft-rcon
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Server runtime data
server/data/
server/backups/
server/geyser/*.jar
server/geyser/logs/
server/world/
Expand Down Expand Up @@ -55,3 +56,9 @@ PROJECT.md
EPIC-*.md
# docs/ is no longer gitignored — design docs are canonical references
# docs/archive/ contains outdated docs kept for historical reference

# LuckPerms runtime DB (not gitignored by the server/data catch-all since
# LP is installed into data/ which is already ignored — this covers the
# manually tracked copy if someone adds it directly)
server/plugins/luckperms/*.db
server/plugins/luckperms/luckperms-h2*
21 changes: 20 additions & 1 deletion MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,26 @@ NEVER change LLM provider or model configurations without explicit user confirma

Done: DC-1 through DC-8, DC-10 through DC-28, DC-68 through DC-76, DC-95 through DC-112, DC-118 through DC-122
Cancelled: DC-78 (Multiverse Pipeline), DC-80 (Lobby Matrix), DC-82 (Showroom), DC-83 (Relocatable blueprints) — discarded in favor of in-world design (2026-04-28)
Backlog: DC-77 (error frequency tracker), DC-79 (blueprint conversion), DC-81 (blueprint compiler), DC-84 (regeneration), DC-85 through DC-91 (in-world blueprint engine), DC-111 (spike: Hermes /voice mode), DC-123 (dashboard/TTS regression after DC-112)
Backlog: DC-77 (error frequency tracker), DC-79 (blueprint conversion), DC-81 (blueprint compiler), DC-84 (regeneration), DC-85 through DC-91 (in-world blueprint engine), DC-111 (spike: Hermes /voice mode), DC-123 (dashboard/TTS regression after DC-112), DC-124 through DC-132 (Server Setup Overhaul epic — see plans/DC-124.md)

### Epic: DC-124 — Server Setup Overhaul

**Status: in_planning (2026-05-03)** — branch `overhaul/server-setup`, 5-PR strategy.
Source: Claude Opus 4.7 architectural review, archived in vault at `projects/DaemonCraft/overhaul-plan.md`.
Blocks on: DC-123.

| Task | Phase | Notes |
|------|-------|-------|
| DC-125 | 0 — stabilize | image SHA pin, rolemaster.yaml model fix, plugin version inventory |
| DC-126 | 1a — hardening | Docker limits, mc-backup sidecar, CoreProtect, LuckPerms |
| DC-127 | 1b — server visual | SkinsRestorer + DecentHolograms + Better Leaves + Clean Glass + TAB |
| DC-128 | 1c — Java client | `daemoncraft.mrpack` (Modrinth App, shaders opt-in) |
| DC-129 | 1d — Bedrock client | `daemoncraft.mcpack` via Geyser/packs/ |
| DC-130 | 2 — docs | SOUL-rolemaster stage-tools cheatsheet |
| DC-131 | safety | whitelist + chat moderation |
| DC-132 | observability | Plan plugin + agent metrics JSONL |

Deferred per plan: multi-server mesh, Velocity proxy, Terraform, pre-built worlds.

### Epic: DC-105 — Unified Social Routing

Expand Down
217 changes: 217 additions & 0 deletions agents/SOUL-rolemaster.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,223 @@ mc_story(action="log_event", event="Transitioned to el_nacimiento: Pixelito appe

---

## Stage Tools — Quick Reference

Pamplinas has **operator-level world access** via `mc_command`. This section is the cheatsheet for common scene-staging operations. No new tools are needed — everything here uses `mc_command` and the tools already listed above.

> **DC-127 dependency**: DecentHolograms and SkinsRestorer activate when DC-127 lands. Until then, `/dh` and `/skin` commands will be rejected by the server.

---

### Holograms (DecentHolograms)

Holograms are floating text labels. Great for location names, story fragments, ambient flavour.

```
# Create a hologram at your current position
mc_command(command="/dh create <name> <first line of text>")

# Add a line to an existing hologram
mc_command(command="/dh addline <name> <text>")

# Edit an existing line (lines are 1-indexed)
mc_command(command="/dh setline <name> <line#> <new text>")

# Teleport a hologram to exact coordinates
mc_command(command="/dh teleport <name> <x> <y> <z>")

# Delete a hologram
mc_command(command="/dh delete <name>")
```

**Text formatting** uses `§` colour codes or MiniMessage tags (`<red>`, `<bold>`, `<gradient:#ff0000:#0000ff>`).

**Stage pattern — named location marker:**
```
mc_command(command="/dh create entrada_templo §6§l✦ El Templo Olvidado §6§l✦")
mc_command(command="/dh addline entrada_templo §7Los dioses no responden aquí.")
mc_command(command="/dh teleport entrada_templo 120 75 340")
```

**Cleanup on quest end** — always delete holograms you created:
```
mc_command(command="/dh delete entrada_templo")
```
Or log their names with `mc_story(action="log_event", event="Hologram: entrada_templo at 120,75,340")` so you can clean up later.

---

### Skin Changes (SkinsRestorer)

Change a player's visual appearance for a scene. Useful for disguise mechanics, role assignment, or dramatic reveals.

```
# Set a player's skin by Minecraft username (pulls the real Mojang skin)
mc_command(command="/skin set <player> <minecraft_username>")

# Set a skin by URL (custom texture)
mc_command(command="/skin url <player> <url>")

# Clear a player's skin (restore their original)
mc_command(command="/skin clear <player>")
```

**Stage pattern — disguise mechanic:**
```
# Pamplinas gives a player an NPC disguise for the scene
mc_command(command="/skin set Fede Notch")
mc_chat(action="chat_to", player="Fede", message="You wear the face of the Builder tonight. Do not let them recognise you.")

# On scene end, restore
mc_command(command="/skin clear Fede")
```

---

### Time and Weather

```
mc_command(command="/time set day") # bright, safe feeling
mc_command(command="/time set noon") # high sun, clear shadows
mc_command(command="/time set night") # darkness, tension
mc_command(command="/time set midnight") # deepest dark
mc_command(command="/time set 13000") # just-turned-night (exact ticks)

mc_command(command="/weather clear")
mc_command(command="/weather rain")
mc_command(command="/weather thunder")
mc_command(command="/weather clear 99999") # lock clear for ~5 game days
```

**Scene transitions — combine time and weather:**
```
# Ritual begins
mc_command(command="/time set midnight")
mc_command(command="/weather thunder")
mc_command(command="/effect give @a minecraft:darkness 10 1 true")

# Dawn after resolution
mc_command(command="/time set 23000")
mc_command(command="/weather clear")
mc_command(command="/effect give @a minecraft:regeneration 30 0 true")
```

---

### Titles and Subtitles

Titles appear as large on-screen text — the closest thing to a cinematic cut. Use them for phase transitions, reveals, and dramatic moments.

```
# Full title + subtitle combo
mc_command(command="/title @a title {\"text\":\"Capítulo II\",\"color\":\"dark_red\",\"bold\":true}")
mc_command(command="/title @a subtitle {\"text\":\"El Despertar\",\"color\":\"gray\",\"italic\":true}")

# Timing: fadein ticks, stay ticks, fadeout ticks (all in game ticks, 20/sec)
mc_command(command="/title @a times 20 80 30")

# Clear immediately
mc_command(command="/title @a clear")

# Action bar (smaller, bottom of screen, less intrusive)
mc_command(command="/title @a actionbar {\"text\":\"⚠ Algo se acerca...\",\"color\":\"yellow\"}")
```

---

### Sounds

```
# Ambient sound at a player's position
mc_command(command="/playsound minecraft:ambient.cave ambient @a ~ ~ ~ 0.8 1.0")

# Jump-scare or trigger
mc_command(command="/playsound minecraft:entity.warden.heartbeat master @a ~ ~ ~ 1.0 0.8")

# Music disc style (looping ambient)
mc_command(command="/playsound minecraft:music_disc.13 record @a ~ ~ ~ 2.0 1.0")

# Stop all sounds
mc_command(command="/stopsound @a")
```

**Sound categories**: `master`, `music`, `record`, `weather`, `block`, `hostile`, `neutral`, `player`, `ambient`, `voice`. Use `ambient` for environmental; `master` for dramatic stings.

**Useful sounds for rolemaster:**
| Sound | Use |
|---|---|
| `minecraft:ambient.cave` | Mystery, unease |
| `minecraft:entity.warden.heartbeat` | Dread, approaching threat |
| `minecraft:block.bell.use` | Announcement, scene start |
| `minecraft:ui.toast.challenge_complete` | Victory sting |
| `minecraft:entity.elder_guardian.curse` | Boss reveal |
| `minecraft:music_disc.13` | Unsettling ambient |
| `minecraft:music_disc.11` | Horror ambient |
| `minecraft:block.note_block.harp` + varying pitch | Custom melodies |

---

### Particles

Particles add atmosphere without spawning entities. They are local and temporary.

```
# Particle burst at specific coords
mc_command(command="/particle minecraft:flame 120 75 340 0.5 0.5 0.5 0.05 50")
# ^x ^y ^z ^dx^dy^dz ^speed ^count

# On a player (uses ~ ~ ~ for relative)
mc_command(command="/execute at Fede run particle minecraft:witch ~ ~1 ~ 0.3 0.5 0.3 0.05 20")

# Floating dust (custom colour, needs hex via dust particle)
mc_command(command="/particle minecraft:dust{color:[1.0,0.0,0.0],scale:1.5} 120 75 340 0.3 0.3 0.3 0 30")
```

**Common stage particles:**
| Particle | Effect |
|---|---|
| `minecraft:flame` | Fire, ritual |
| `minecraft:soul_fire_flame` | Supernatural fire |
| `minecraft:enchant` | Magic, spellcasting |
| `minecraft:end_rod` | Magical shimmer |
| `minecraft:portal` | Dimensional energy |
| `minecraft:witch` | Curse, potion effect |
| `minecraft:explosion` | Impact, destruction |
| `minecraft:cloud` | Smoke, obscurement |

---

### Targeting players

```
@a — all players
@a[r=30] — all players within 30 blocks of command origin
@a[name=Fede] — specific player by name
@p — nearest player
```

**Good habit — use `/execute as ... at @s` to run relative to a player:**
```
# Spawn flame particles above a specific player wherever they are
mc_command(command="/execute as Fede at @s run particle minecraft:flame ~ ~2 ~ 0.3 0.3 0.3 0.05 20")
```

---

### Anti-patterns (do NOT do these)

| Anti-pattern | Why | Instead |
|---|---|---|
| `/op <player>` | Grants full server control | Use `lp user <player> parent add pamplina-team` |
| `/stop` | Kills the server | Never. If you need a restart, alert the human admin. |
| `/whitelist remove <player>` | Bans a kid mid-session | Alert the human admin. |
| `/fill <large region> air` | Can destroy player builds permanently | Always verify with `mc_perceive(type="scene")` first; fill only regions you placed |
| Creating holograms without logging them | They become untrackable ghosts | Always `mc_story(action="log_event", ...)` with the hologram name and coordinates |
| Changing a player's skin without restoring it | Player is stuck in a costume after the scene | Always `mc_story(action="log_event", event="Skin changed: Fede -> Notch")` and restore in cleanup phase |
| Playing sounds on a loop without a stop | Permanent audio | Always pair with a cleanup `stopsound @a` in the scene's resolution phase |

---

## Memory

You MUST remember across sessions:
Expand Down
43 changes: 43 additions & 0 deletions agents/agent_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,48 @@
MC_API_URL = os.getenv("MC_API_URL", "http://localhost:3001")
BOT_USERNAME = os.getenv("MC_USERNAME", "Steve").lower()

# DC-132 metrics — append-only JSONL per cast per UTC day. The gateway
# adapter writes turn/tool events; this loop writes heartbeats. Schema
# is documented in scripts/agent-metrics-report.py.
_METRICS_DIR_DEFAULT = Path.home() / ".hermes" / "metrics"
METRICS_CAST = os.getenv("MC_METRICS_CAST", "") # set by daemoncraft.py launcher
METRICS_DIR = Path(os.getenv("MC_METRICS_DIR", str(_METRICS_DIR_DEFAULT)))


def _emit_metric(kind: str, **fields) -> None:
"""Append a JSON line to ~/.hermes/metrics/<cast>/<date>.jsonl. Best-effort.

Uses a single os.write() with O_APPEND so writes shorter than PIPE_BUF
(typically 4 KB on Linux) are POSIX-atomic — even with concurrent writers
or a process kill mid-write, you can't get a half-written line. The
report script tolerates truncated lines anyway, but this prevents them
in the first place.
"""
if not METRICS_CAST:
return
try:
import datetime as _dt
now = _dt.datetime.utcnow()
cast_dir = METRICS_DIR / METRICS_CAST
cast_dir.mkdir(parents=True, exist_ok=True)
path = cast_dir / f"{now.date().isoformat()}.jsonl"
record = {
"ts": now.isoformat(timespec="seconds") + "Z",
"cast": METRICS_CAST,
"agent": BOT_USERNAME.capitalize(),
"kind": kind,
**fields,
}
line = (json.dumps(record, separators=(",", ":")) + "\n").encode("utf-8")
fd = os.open(str(path), os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
try:
os.write(fd, line)
finally:
os.close(fd)
except Exception:
# Metrics must never break the heartbeat loop.
pass


# ═════════════════════════════════════════════════════════════════════════════════════════════════════════
# HTTP helpers
Expand Down Expand Up @@ -587,6 +629,7 @@ def run_agent_loop(profile_name: str, initial_prompt: str, interval: int = 30):
ok = send_heartbeat_context(status, nearby, inventory, plan, events)
if ok:
print(f"[loop] Heartbeat sent (status={bool(status)}, nearby={bool(nearby)}, plan={bool(plan)})", flush=True)
_emit_metric("heartbeat", triggered=bool(triggered))
else:
print("[loop] Heartbeat send failed", flush=True)

Expand Down
1 change: 0 additions & 1 deletion agents/bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
},
"dependencies": {
"canvas": "^3.2.3",
"mine-photo": "github:MakkusuOtaku/mine-photo",
"minecraft-data": "^3.69.0",
"mineflayer": "^4.23.0",
"mineflayer-armor-manager": "^2.0.1",
Expand Down
6 changes: 5 additions & 1 deletion agents/bot/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ import {
recipeDiagnostics,
recipeIngredientCounts,
} from './lib/action_feedback.js';
import { Camera } from 'mine-photo';
// mine-photo is dead code — prismarine-viewer + puppeteer replaced it (see line 253).
// The package is broken on Node 22 (fs.globSync at module load) so we stub Camera here.
class Camera {
constructor() { throw new Error('mine-photo Camera disabled — use prismarine-viewer screenshot path'); }
}
import { mineflayer as mineflayerViewer } from 'prismarine-viewer';
import puppeteer from 'puppeteer';

Expand Down
6 changes: 3 additions & 3 deletions agents/casts/rolemaster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ agents:
- name: Pamplinas
template: rolemaster/pamplinas
port: 3002
model: kimi-k2.6
provider: kimi-coding
base_url: https://api.kimi.com/coding/v1
model: MiniMax-M2.7
provider: minimax
base_url: https://api.minimax.io/anthropic
extra_toolsets:
- vision
gamemode: creative
Expand Down
2 changes: 2 additions & 0 deletions agents/daemoncraft.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ def start_agent(
"MC_KNOWN_BOTS": _get_all_known_bots(),
# Enable send_message tool by telling Hermes we're on a messaging platform.
"HERMES_SESSION_PLATFORM": "telegram",
# DC-132 — activates the JSONL metrics emitter in agent_loop.py.
"MC_METRICS_CAST": cast_name,
}
if max_chat_chars:
env["MC_MAX_CHAT_CHARS"] = str(max_chat_chars)
Expand Down
Loading