import base64
import hashlib
import html
import json
import math
import os
import tempfile
import zipfile
from dataclasses import dataclass
from datetime import datetime, timezone
from io import BytesIO
import gradio as gr
import numpy as np
from PIL import Image, ImageDraw, ImageFont
MODEL_ID = "dreamwall-local-semantic-fingerprint-v1"
GRID = 32
SCALE = 12
PUBLIC_SPACE_URL = "https://build-small-hackathon-dreamwall-mc.hf.space"
RESOURCE_PACK_URL = "https://huggingface.co/spaces/build-small-hackathon/dreamwall-mc/resolve/main/resource-pack/AfterBlockMuseum.zip"
RESOURCE_PACK_SHA1 = "03487f018e2062e254b5ea443396f29d099f8b67"
RESOURCE_PACK_PATH = "resource-pack/AfterBlockMuseum.zip"
PAPER_PLUGIN_JAR_PATH = "server-kit/dreamwall-paper-bridge-0.1.0.jar"
PAPER_PLUGIN_SHA1 = "fd0d87ebcebe6749cbd7023c44d7c7b2c99e2170"
PAPER_PLUGIN_SHA256 = "22e99a4205b07e9df386519e2c19a14e56aa775fe69c14c77771ac996387cc44"
PREBUILT_WORLD_PATH = "server-kit/afterblock-demo-world.zip"
PREBUILT_WORLD_SHA1 = "c438d7dcb98493f436e0ca32aa6c8f73035fbdc0"
PREBUILT_WORLD_SHA256 = "e4634fb17b6aefcb1f075701727cb3c34bb94ce1886dcbb6c729dc0cb4515a6a"
TEXTURE_PAGE_SIZE = 36
DEFAULT_IMPORT_PROMPT = "blue school bag from exam week"
DEFAULT_IMPORT_STORY = "It carried my laptop, exam panic, snacks, and the mornings I kept showing up."
DEFAULT_IMPORT_OWNER = "@Wildstash"
PEBBLEHOST_SFTP_HOST = "uk144.pebblehost.net"
PEBBLEHOST_SFTP_PORT = "2222"
PEBBLEHOST_SFTP_USER = "itsarnavsalkade@gmail.com.5ea1f567"
MINECRAFT_JOIN_ADDRESS = os.getenv("AFTERBLOCK_MINECRAFT_JOIN_ADDRESS", "uk144.pebblehost.net")
MINECRAFT_JOIN_NOTE = os.getenv(
"AFTERBLOCK_MINECRAFT_JOIN_NOTE",
"Use the Minecraft port shown in the PebbleHost panel. SFTP port 2222 is only for uploads.",
)
BLOCKS = [
("white_wool", (234, 236, 230)),
("black_wool", (25, 25, 25)),
("gray_wool", (78, 82, 86)),
("light_gray_wool", (156, 160, 162)),
("brown_wool", (114, 73, 43)),
("red_wool", (160, 39, 34)),
("orange_wool", (230, 118, 31)),
("yellow_wool", (246, 198, 45)),
("lime_wool", (96, 187, 50)),
("green_wool", (74, 124, 42)),
("cyan_wool", (22, 156, 156)),
("light_blue_wool", (92, 168, 224)),
("blue_wool", (53, 70, 164)),
("purple_wool", (126, 61, 181)),
("magenta_wool", (190, 67, 181)),
("pink_wool", (239, 141, 172)),
("sandstone", (218, 203, 143)),
("moss_block", (89, 110, 45)),
("deepslate", (62, 62, 68)),
("amethyst_block", (133, 89, 184)),
("prismarine", (99, 156, 151)),
("glowstone", (241, 203, 118)),
("obsidian", (28, 22, 38)),
("sea_lantern", (172, 205, 190)),
]
MOOD_WORDS = {
"cozy": ["cozy", "warm", "cottage", "soft", "home", "lantern"],
"cursed": ["cursed", "haunted", "eldritch", "broken", "void", "forbidden"],
"ancient": ["ancient", "ruin", "temple", "fossil", "myth", "buried"],
"mechanical": ["machine", "gear", "factory", "robot", "engine", "circuit"],
"wild": ["forest", "storm", "moss", "ocean", "swamp", "wind"],
"royal": ["castle", "king", "queen", "gold", "throne", "banner"],
}
CANVAS_SIZE = 12
PLOT_SCALE = 32
GALLERY_ORIGIN_X = -(CANVAS_SIZE // 2) * PLOT_SCALE
GALLERY_ORIGIN_Y = 80
GALLERY_ORIGIN_Z = -(CANVAS_SIZE // 2) * PLOT_SCALE
EXISTING_ARTWORKS = [
{
"title": "Bird above the broken sky",
"player": "anonymous_heron",
"prompt": "a bird in the sky over a silver tree",
"x": 4,
"z": 5,
"moods": ["wild", "cozy"],
"value": 72,
},
{
"title": "Company sigil in emerald glass",
"player": "founder_ghost",
"prompt": "an ai logo for my company made of emerald glass",
"x": 5,
"z": 5,
"moods": ["mechanical", "royal"],
"value": 81,
},
{
"title": "Cloud treaty",
"player": "sky_bidder",
"prompt": "clouds gathering around a public tree",
"x": 5,
"z": 4,
"moods": ["wild", "ancient"],
"value": 64,
},
{
"title": "Nether receipt",
"player": "redacted",
"prompt": "a cursed vending machine that sells memories",
"x": 8,
"z": 8,
"moods": ["cursed", "mechanical"],
"value": 69,
},
]
LIVING_WALL_PROMPTS = [
"a lonely moon whale carrying broken radio signals",
"a neon forest growing through an old arcade machine",
"a thunder bird protecting a tiny public library",
"glass mushrooms orbiting a redstone lighthouse",
"a crown made of moss floating over a village gate",
"a cloud dragon sleeping inside a circuit board",
]
MUSEUM_HALLS = [
"Hall of Firsts",
"Hall of Companions",
"Hall of Turning Points",
"Hall of Worlds",
"Hall of Soft Things",
"Hall of Tools",
"Hall of Lost Signals",
"Grand Painting Hall",
"Animal Spirit Grove",
]
HALL_PROFILES = {
"Hall of Firsts": {
"when": "when an object marks a first threshold",
"world": "sunlit origin cabinets, first-step lamps, gold plaques",
"minecraft": "warm sandstone, gold pressure plates, first-light redstone lamps",
},
"Hall of Companions": {
"when": "when something stayed close through ordinary days",
"world": "pocket pedestals, quiet benches, green glass cases",
"minecraft": "moss blocks, item frames, low lanterns near visitor paths",
},
"Hall of Turning Points": {
"when": "when a choice, exam, trade, or pressure changed direction",
"world": "angled corridors, copper rails, redstone decision marks",
"minecraft": "copper blocks, comparator lamps, signs with before/after lines",
},
"Hall of Worlds": {
"when": "when a relic opened a world larger than the room",
"world": "portal shelves, blue map floors, miniature skylines",
"minecraft": "blue wool, maps, frames, glowstone portal corners",
},
"Hall of Soft Things": {
"when": "when the memory protects, quiets, or shelters",
"world": "felt-lit corners, soft rails, low pink glass",
"minecraft": "pink wool, beds, candles, carpeted alcoves",
},
"Hall of Tools": {
"when": "when the object helped the owner act on the world",
"world": "workbench rows, grey vaults, calibrated stands",
"minecraft": "deepslate, anvils, crafting tables, inspection signs",
},
"Hall of Lost Signals": {
"when": "when private signal survives public noise",
"world": "dim receivers, violet static, archive slits",
"minecraft": "amethyst, note blocks, redstone repeaters, low particles",
},
"Grand Painting Hall": {
"when": "when imagination becomes a place",
"world": "large frame walls, myth bays, painted sky panels",
"minecraft": "item frames, terracotta walls, banner rails, map-art slots",
},
"Animal Spirit Grove": {
"when": "when the artifact behaves like a living companion",
"world": "moss pens, moonlit tracks, creature-name stones",
"minecraft": "moss carpets, fences, leaves, particle trails",
},
}
DEMO_ARTIFACTS = {
"Star Wars childhood book": {
"owner_name": "Arnav",
"owner_handle": "@Wildstash",
"input_type": "memory_prompt",
"source_prompt": "a worn Star Wars childhood book with bent corners and a silver spaceship on the cover",
"memory_text": "I kept reopening it because the galaxy felt bigger than my room.",
},
"AirPods from first year of university": {
"owner_name": "Arnav",
"owner_handle": "@Wildstash",
"input_type": "object_photo",
"source_prompt": "white AirPods from my first year of university",
"memory_text": "They carried private worlds through public noise during my first year away.",
},
"Monitor used for first Bitcoin trade": {
"owner_name": "Arnav",
"owner_handle": "@Wildstash",
"input_type": "object_photo",
"source_prompt": "a black monitor used for my first Bitcoin trade",
"memory_text": "A screen where numbers first started feeling like weather.",
},
"School bag carried through exams": {
"owner_name": "Arnav",
"owner_handle": "@Wildstash",
"input_type": "object_photo",
"source_prompt": "a school bag carried through exams",
"memory_text": "It held pencils, snacks, panic, and the weight of every morning.",
},
"Prompted painting: red dragon above childhood home": {
"owner_name": "Arnav",
"owner_handle": "@Wildstash",
"input_type": "painting_prompt",
"source_prompt": "a red dragon above my childhood home",
"memory_text": "A fantasy guardian hovering over the place I learned to imagine from.",
},
}
SOCIAL_HANDLES = [
"@mossbyte", "@skyreceipt", "@quietbag", "@pixelmira", "@redstoneivy",
"@lostadapter", "@softsignal", "@bookishnova", "@lampkeeper", "@tinymoon",
"@schoolghost", "@mintmonitor", "@cloudpocket", "@driftcase", "@noiseferry",
"@glasssparrow", "@examrunner", "@coppernote", "@homebutton", "@tidyvoid",
]
OBJECT_LIBRARY = [
("Star Wars childhood book", "memory_prompt", "a worn Star Wars childhood book with bent corners", "I kept reopening it because the galaxy felt bigger than my room.", "book"),
("AirPods", "object_photo", "white AirPods from first year of university", "They carried private worlds through public noise.", "earbuds"),
("Bitcoin monitor", "object_photo", "black monitor used for a first Bitcoin trade", "Numbers first started feeling like weather.", "monitor"),
("Exam school bag", "object_photo", "school bag carried through exams", "It held pencils, snacks, panic, and every morning.", "school bag"),
("Red dragon painting", "painting_prompt", "a red dragon above my childhood home", "A fantasy guardian over the place I learned to imagine.", "painting"),
("Water bottle", "object_photo", "scratched blue water bottle", "It waited on every desk like a tiny reservoir.", "water bottle"),
("Desk lamp", "object_photo", "warm desk lamp beside late night notes", "It kept one square of the room awake.", "lamp"),
("Microphone", "object_photo", "black microphone used for first recordings", "It learned my voice before anyone else did.", "microphone"),
("Tissue packet", "object_photo", "small tissue packet from a long train ride", "It was ordinary until the day was not.", "tissue"),
("Keyboard", "object_photo", "keyboard with shiny worn keys", "It translated nervous thoughts into work.", "keyboard"),
("Phone charger", "object_photo", "fraying white phone charger", "It rescued the last percent of the day.", "charger"),
("Coffee mug", "object_photo", "ceramic coffee mug with a chipped handle", "It warmed decisions before they became plans.", "mug"),
("Running shoes", "object_photo", "old running shoes with rain marks", "They carried promises the body almost kept.", "shoes"),
("House key", "object_photo", "single house key on a faded ring", "It made a door feel like a return.", "key"),
("Toy car", "memory_prompt", "tiny red toy car from a childhood shelf", "It was speed before I knew distance.", "toy car"),
("Notebook", "object_photo", "notebook full of half-started ideas", "It preserved versions of me that did not ship.", "notebook"),
("Glasses", "object_photo", "black glasses with a scratched lens", "They made the world sharper and less certain.", "glasses"),
("Wallet", "object_photo", "old wallet with expired cards", "It held small permissions to be outside.", "wallet"),
("USB drive", "object_photo", "silver USB drive with unknown files", "It carries a sealed room of forgotten work.", "usb drive"),
("Alarm clock", "object_photo", "small alarm clock from exam mornings", "It was the sound of responsibility arriving.", "clock"),
("Calculator", "object_photo", "scientific calculator with faded buttons", "It made difficult answers feel briefly mechanical.", "calculator"),
("Camera", "object_photo", "compact camera with a scratched lens", "It saved proof that the room once looked different.", "camera"),
("Cassette tape", "object_photo", "old cassette tape with a handwritten label", "It held a voice from before everything became searchable.", "cassette"),
("Lucky coin", "object_photo", "small lucky coin kept in a wallet", "It was not worth much until it became a ritual.", "coin"),
("Game controller", "object_photo", "game controller with worn thumbsticks", "It made imaginary places answer back.", "game controller"),
("Headphones", "object_photo", "black headphones used on long nights", "They made a private room around the head.", "headphones"),
("Lunch box", "object_photo", "steel lunch box from school days", "It carried home into the middle of the day.", "lunch box"),
("Medicine box", "object_photo", "small medicine box in a drawer", "It remembered the days the body needed backup.", "medicine box"),
("Memory card", "object_photo", "tiny memory card with old photos", "It kept a city of images in almost no space.", "memory card"),
("Paint brush", "object_photo", "paint brush stained with blue acrylic", "It turned hesitation into visible color.", "paint brush"),
("Passport", "object_photo", "passport with airport stamps", "It proved the same person could cross many rooms.", "passport"),
("Pencil", "object_photo", "short pencil chewed near the eraser", "It carried ideas before they were confident.", "pencil"),
("Photo frame", "object_photo", "wooden photo frame beside a bed", "It made one memory stand still on purpose.", "photo frame"),
("Plush toy", "object_photo", "soft plush toy from an old shelf", "It was a guardian that never needed batteries.", "plush toy"),
("Polaroid", "object_photo", "faded polaroid from a bright afternoon", "It made a square of time feel touchable.", "polaroid"),
("Remote", "object_photo", "TV remote with one missing button", "It controlled a room more than it should have.", "remote"),
("Ring", "object_photo", "simple ring with a small scratch", "It made a promise small enough to wear.", "ring"),
("Train ticket", "object_photo", "creased train ticket from a late ride", "It marked the distance between two versions of the day.", "train ticket"),
("Umbrella", "object_photo", "black umbrella with bent ribs", "It made bad weather negotiable.", "umbrella"),
("Watch", "object_photo", "old watch that runs a little slow", "It kept time in its own accent.", "watch"),
]
@dataclass
class ArtResult:
image: Image.Image
profile: dict
palette: list
commands: str
report: str
trace: str
server_packet: str
canvas_report: str
valuation_packet: str
def stable_seed(text: str) -> int:
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
return int(digest[:16], 16)
def embedding(text: str) -> np.ndarray:
lowered = text.lower()
vec = np.zeros(96, dtype=np.float32)
words = [word.strip(".,!?;:()[]{}\"'") for word in lowered.split()]
for idx, word in enumerate(words):
if not word:
continue
digest = hashlib.blake2b(word.encode("utf-8"), digest_size=32).digest()
for offset, byte in enumerate(digest):
slot = (byte + idx * 17 + offset * 7) % len(vec)
vec[slot] += ((byte / 255.0) * 2.0 - 1.0) * (1.0 + min(len(word), 12) / 12.0)
for mood, mood_words in MOOD_WORDS.items():
hits = sum(1 for word in mood_words if word in lowered)
if hits:
mood_seed = stable_seed(mood)
rng = np.random.default_rng(mood_seed)
vec += rng.normal(0, 0.22 * hits, size=len(vec)).astype(np.float32)
if not np.any(vec):
vec[0] = 1.0
norm = float(np.linalg.norm(vec))
return vec / max(norm, 1e-6)
def top_moods(text: str, vec: np.ndarray) -> list[str]:
lowered = text.lower()
scored = []
for mood, words in MOOD_WORDS.items():
lexical = sum(1 for word in words if word in lowered) * 0.28
mood_vec = embedding(" ".join(words))
semantic = float(np.dot(vec, mood_vec))
scored.append((semantic + lexical, mood))
return [mood for _, mood in sorted(scored, reverse=True)[:3]]
def palette_from_vector(vec: np.ndarray, seed: int, moods: list[str]) -> list[tuple[str, tuple[int, int, int]]]:
rng = np.random.default_rng(seed)
block_vecs = np.array([rgb for _, rgb in BLOCKS], dtype=np.float32) / 255.0
anchors = np.abs(vec[: len(BLOCKS)]) + 1e-4
weights = anchors / max(float(anchors.sum()), 1e-6)
chosen = list(rng.choice(len(BLOCKS), size=7, replace=False, p=weights))
mood_boosts = {
"cozy": ["orange_wool", "yellow_wool", "glowstone", "brown_wool"],
"cursed": ["obsidian", "purple_wool", "black_wool", "amethyst_block"],
"ancient": ["sandstone", "moss_block", "deepslate", "brown_wool"],
"mechanical": ["gray_wool", "light_gray_wool", "deepslate", "cyan_wool"],
"wild": ["moss_block", "green_wool", "prismarine", "sea_lantern"],
"royal": ["yellow_wool", "red_wool", "purple_wool", "blue_wool"],
}
for mood in moods:
for name in mood_boosts.get(mood, []):
idx = next(i for i, block in enumerate(BLOCKS) if block[0] == name)
if idx not in chosen:
chosen[-1] = idx
break
return [BLOCKS[i] for i in chosen]
def generate_grid(vec: np.ndarray, seed: int, palette: list) -> np.ndarray:
rng = np.random.default_rng(seed)
grid = np.zeros((GRID, GRID), dtype=np.int32)
freq_a = 1.4 + abs(vec[3]) * 5
freq_b = 1.2 + abs(vec[9]) * 4
symmetry = abs(vec[12]) > 0.19
center_bias = abs(vec[27])
for y in range(GRID):
for x in range(GRID):
nx = (x / GRID) - 0.5
ny = (y / GRID) - 0.5
wave = math.sin((nx * freq_a + vec[1]) * math.pi * 2)
wave += math.cos((ny * freq_b + vec[2]) * math.pi * 2)
ring = math.sin((math.hypot(nx, ny) * (6 + abs(vec[18]) * 12) + vec[4]) * math.pi)
noise = rng.normal(0, 0.42)
score = wave + ring * (0.7 + center_bias) + noise
idx = int(abs(score * 997 + vec[(x + y) % len(vec)] * 113)) % len(palette)
grid[y, x] = idx
if symmetry:
grid[:, GRID // 2 :] = np.fliplr(grid[:, : GRID // 2])
return grid
def render_grid(grid: np.ndarray, palette: list) -> Image.Image:
img = Image.new("RGB", (GRID * SCALE, GRID * SCALE), (0, 0, 0))
draw = ImageDraw.Draw(img)
for y in range(GRID):
for x in range(GRID):
_, color = palette[int(grid[y, x])]
draw.rectangle(
[x * SCALE, y * SCALE, (x + 1) * SCALE - 1, (y + 1) * SCALE - 1],
fill=color,
)
for i in range(0, GRID * SCALE, SCALE * 4):
draw.line([(i, 0), (i, GRID * SCALE)], fill=(35, 28, 22), width=1)
draw.line([(0, i), (GRID * SCALE, i)], fill=(35, 28, 22), width=1)
return img
def compact_commands(grid: np.ndarray, palette: list, origin: str) -> str:
commands = [
"# Paste these into Minecraft with WorldEdit installed.",
"# Stand near the gallery wall. Set pos1/pos2 manually if needed.",
f"# Suggested origin: {origin}",
"//wand",
"//pos1",
"//pos2",
"# Build the 32x32 mural as wool/block stripes. Each line is one row.",
]
for y in range(GRID):
runs = []
start = 0
current = int(grid[y, 0])
for x in range(1, GRID + 1):
if x == GRID or int(grid[y, x]) != current:
block = palette[current][0]
runs.append(f"{start}-{x - 1}:{block}")
if x < GRID:
start = x
current = int(grid[y, x])
commands.append(f"# row {y:02d}: " + ", ".join(runs))
commands.append("# Plugin hook idea: convert the row runs into setblock/fill calls at the wall anchor.")
return "\n".join(commands)
def row_runs(grid: np.ndarray, palette: list) -> list[list[dict]]:
rows = []
for y in range(GRID):
runs = []
start = 0
current = int(grid[y, 0])
for x in range(1, GRID + 1):
if x == GRID or int(grid[y, x]) != current:
runs.append(
{
"x1": start,
"x2": x - 1,
"y": y,
"block": palette[current][0],
}
)
if x < GRID:
start = x
current = int(grid[y, x])
rows.append(runs)
return rows
def prompt_density(prompt: str) -> float:
words = [word.strip(".,!?;:()[]{}\"'").lower() for word in prompt.split()]
words = [word for word in words if word]
if not words:
return 0.0
unique_ratio = len(set(words)) / len(words)
long_word_ratio = sum(1 for word in words if len(word) >= 7) / len(words)
symbol_hits = sum(1 for word in words if word in {"bird", "tree", "cloud", "logo", "castle", "machine", "temple", "sky"})
return min(1.0, unique_ratio * 0.55 + long_word_ratio * 0.25 + min(symbol_hits, 4) * 0.05)
def plot_for_seed(seed: int) -> dict:
x = seed % CANVAS_SIZE
z = (seed // CANVAS_SIZE) % CANVAS_SIZE
return {
"x": int(x),
"z": int(z),
"world_x": int((x - CANVAS_SIZE // 2) * PLOT_SCALE),
"world_z": int((z - CANVAS_SIZE // 2) * PLOT_SCALE),
"size": PLOT_SCALE,
}
def nearby_artworks(plot: dict) -> list[dict]:
near = []
for art in EXISTING_ARTWORKS:
distance = abs(art["x"] - plot["x"]) + abs(art["z"] - plot["z"])
if distance <= 2:
near.append({**art, "distance": distance})
return sorted(near, key=lambda item: (item["distance"], -item["value"]))[:3]
def fusion_lines(prompt: str, player: str, moods: list[str], plot: dict) -> list[str]:
neighbors = nearby_artworks(plot)
if not neighbors:
return [
"No nearby fusion yet. This plot becomes a new anchor others can build around.",
"Value grows if future prompts land nearby and reuse its symbols.",
]
lines = []
for art in neighbors:
shared_moods = sorted(set(moods).intersection(art["moods"]))
if shared_moods:
reason = f"shared {', '.join(shared_moods)} mood"
else:
reason = "spatial collision without mood overlap"
lines.append(
f"{player} fuses with {art['player']} at ({art['x']}, {art['z']}): "
f"{reason}. New concept: {prompt} woven into '{art['title']}'."
)
return lines
def valuation(prompt: str, moods: list[str], palette_names: list[str], plot: dict) -> dict:
density = prompt_density(prompt)
neighbors = nearby_artworks(plot)
adjacency = min(1.0, sum(max(0, 3 - item["distance"]) for item in neighbors) / 6)
mood_diversity = len(set(moods)) / max(1, len(MOOD_WORDS))
palette_rarity = len(set(palette_names).intersection({"obsidian", "amethyst_block", "sea_lantern", "glowstone"})) / 4
score = 25 + density * 28 + adjacency * 24 + mood_diversity * 12 + palette_rarity * 16
votes = int(3 + score // 8 + len(neighbors) * 2)
reserve = int(max(5, score * 1.7))
return {
"creative_value": round(score, 2),
"syntactic_density": round(density, 3),
"context_adjacency": round(adjacency, 3),
"mood_diversity": round(mood_diversity, 3),
"palette_rarity": round(palette_rarity, 3),
"suggested_votes": votes,
"demo_reserve_points": reserve,
"market_note": "Demo points only; no real-money sale or blockchain required for the hackathon.",
}
def canvas_report(prompt: str, player: str, moods: list[str], palette_names: list[str], plot: dict) -> tuple[str, str]:
value = valuation(prompt, moods, palette_names, plot)
fusions = fusion_lines(prompt, player, moods, plot)
report = [
f"Plot assigned: ({plot['x']}, {plot['z']}) -> Minecraft origin ({plot['world_x']}, 80, {plot['world_z']})",
f"Creative value: {value['creative_value']} demo points",
f"Suggested opening auction reserve: {value['demo_reserve_points']} demo points",
"",
"Why this plot has value:",
f"- syntactic density: {value['syntactic_density']}",
f"- context adjacency: {value['context_adjacency']}",
f"- mood diversity: {value['mood_diversity']}",
f"- palette rarity: {value['palette_rarity']}",
"",
"Fusion events:",
]
report.extend(f"- {line}" for line in fusions)
packet = {
"protocol": "dreamwall.market.v1",
"plot": plot,
"valuation": value,
"fusion_events": fusions,
"auction": {
"mode": "demo_points",
"reserve": value["demo_reserve_points"],
"votes": value["suggested_votes"],
"real_money": False,
"blockchain": False,
},
}
return "\n".join(report), json.dumps(packet, indent=2)
def object_guess_for(input_type: str, source_prompt: str, memory_text: str) -> str:
text = f"{source_prompt} {memory_text}".lower()
guesses = [
("phone", ["phone", "iphone", "android", "smartphone", "mobile"]),
("water bottle", ["water bottle", "waater", "bottle", "canteen", "thermos"]),
("plush toy", ["teddy", "teddy bear", "bear", "plush", "soft toy", "stuffed"]),
("game controller", ["xbox controller", "xbox", "controller", "gamepad", "game controller", "console controller"]),
("sticker", ["sticker", "decal", "label", "logo", "openai"]),
("shoes", ["shoe", "shoes", "sneaker", "sneakers", "trainers"]),
("book", ["book", "novel", "comic", "star wars", "cover"]),
("earbuds", ["airpods", "earpods", "earbuds", "headphones", "music"]),
("monitor", ["monitor", "screen", "display", "trade", "bitcoin"]),
("school bag", ["bag", "backpack", "exams", "school"]),
("painting", ["painting", "dragon", "prompted", "canvas"]),
("animal spirit", ["pet", "animal", "dog", "cat", "bird", "creature"]),
("tool", ["tool", "keyboard", "mouse", "camera", "phone"]),
]
for guess, hints in guesses:
if any(hint in text for hint in hints):
return guess
if input_type == "painting_prompt":
return "prompted painting"
if input_type == "animal_spirit":
return "animal spirit"
return "personal relic"
def hall_for_artifact(input_type: str, object_guess: str, source_prompt: str, memory_text: str, seed: int) -> tuple[str, str]:
text = f"{object_guess} {source_prompt} {memory_text}".lower()
if input_type == "painting_prompt":
return "Grand Painting Hall", "paintings that were imagined before they were found"
if input_type == "animal_spirit" or any(word in text for word in ["pet", "animal", "dog", "cat", "bird", "creature"]):
return "Animal Spirit Grove", "the artifact behaves more like a companion than an object"
if any(word in text for word in ["first", "university", "bitcoin", "started"]):
return "Hall of Firsts", "the memory marks a first threshold"
if any(word in text for word in ["teddy", "plush", "soft toy", "sleep", "guardian"]):
return "Hall of Soft Things", "the memory is quiet, protective, and intimate"
if any(word in text for word in ["sticker", "decal", "logo", "signal", "lost", "radio", "noise"]):
return "Hall of Lost Signals", "the artifact carries private signal through public static"
if any(word in text for word in ["xbox", "controller", "gamepad", "console", "thumbstick"]):
return "Hall of Worlds", "it made imaginary worlds respond to the owner's hands"
if any(word in text for word in ["airpods", "bag", "bottle", "water", "phone", "shoe", "friend", "carried", "companion"]):
return "Hall of Companions", "it stayed close to the owner through ordinary days"
if any(word in text for word in ["exam", "trade", "turning", "changed", "panic"]):
return "Hall of Turning Points", "the object sits near a decision or pressure point"
if any(word in text for word in ["star wars", "galaxy", "home", "world", "dragon"]):
return "Hall of Worlds", "it opened a world larger than the room around it"
if any(word in text for word in ["soft", "noise", "private", "morning"]):
return "Hall of Soft Things", "the memory is quiet, protective, and intimate"
if any(word in text for word in ["monitor", "tool", "screen", "keyboard", "camera"]):
return "Hall of Tools", "the artifact helped the owner act on the world"
hall = MUSEUM_HALLS[seed % len(MUSEUM_HALLS)]
return hall, "the museum placed it by symbolic resonance"
def museum_zone_for(hall: str, seed: int) -> str:
zones = {
"Hall of Firsts": ["threshold alcove", "first-light row", "origin cabinet"],
"Hall of Companions": ["pocket gallery", "everyday pedestal", "quiet bench"],
"Hall of Turning Points": ["pressure corridor", "exam arch", "decision stair"],
"Hall of Worlds": ["portal bay", "map room", "myth shelf"],
"Hall of Soft Things": ["hush wing", "warm glass case", "felt-lit corner"],
"Hall of Tools": ["workbench row", "instrument vault", "copper desk"],
"Hall of Lost Signals": ["static aisle", "radio archive", "dim receiver"],
"Grand Painting Hall": ["large frame wall", "dragon bay", "painted sky"],
"Animal Spirit Grove": ["moss enclosure", "companion path", "moonlit pen"],
}
choices = zones.get(hall, ["west wing"])
return choices[seed % len(choices)]
def curation_scores(
source_prompt: str,
memory_text: str,
input_type: str,
moods: list[str],
palette_names: list[str],
plot: dict,
) -> dict:
text = f"{source_prompt} {memory_text}".lower()
density = prompt_density(text)
neighbors = nearby_artworks(plot)
adjacency = min(1.0, sum(max(0, 3 - item["distance"]) for item in neighbors) / 6)
palette_rarity = len(set(palette_names).intersection({"obsidian", "amethyst_block", "sea_lantern", "glowstone"})) / 4
def score(words: list[str], base: float = 28.0) -> int:
hits = sum(1 for word in words if word in text)
return int(min(100, base + density * 35 + hits * 14))
scores = {
"nostalgia": score(["childhood", "school", "first", "home", "kept", "morning"]),
"symbolism": score(["dragon", "galaxy", "signal", "cover", "weather", "guardian"]),
"identity": score(["my", "first", "owner", "university", "trade"], 34),
"companionship": score(["carried", "airpods", "bag", "private", "through"], 30),
"transformation": score(["first", "trade", "exam", "changed", "started"], 26),
"rarity": int(min(100, 35 + len(set(moods)) * 10 + density * 25)),
"palette_rarity": int(round(palette_rarity * 100)),
"adjacency_resonance": int(round(adjacency * 100)),
"visitor_echoes": int(min(100, 12 + len(neighbors) * 17 + density * 34)),
}
weighted = (
scores["nostalgia"] * 0.16
+ scores["symbolism"] * 0.14
+ scores["identity"] * 0.14
+ scores["companionship"] * 0.12
+ scores["transformation"] * 0.12
+ scores["rarity"] * 0.1
+ scores["palette_rarity"] * 0.08
+ scores["adjacency_resonance"] * 0.08
+ scores["visitor_echoes"] * 0.06
)
scores["curation_score"] = int(round(weighted))
return scores
def spirit_for_artifact(title: str, object_guess: str, memory_text: str, hall: str, seed: int) -> dict:
traits_bank = {
"book": ["wide-eyed", "paper-worn", "portal-minded"],
"earbuds": ["private", "signal-carrying", "soft-spoken"],
"monitor": ["watchful", "electric", "threshold-bound"],
"school bag": ["patient", "burdened", "loyal"],
"phone": ["signal-lit", "pocket-sized", "watchful"],
"water bottle": ["clear", "steady", "everyday"],
"plush toy": ["soft", "protective", "sleep-worn"],
"game controller": ["responsive", "world-opening", "thumb-worn"],
"sticker": ["signal-marked", "thin", "identity-bound"],
"shoes": ["road-worn", "patient", "routine-bound"],
"painting": ["mythic", "paint-lit", "protective"],
"animal spirit": ["restless", "companionable", "wild"],
"personal relic": ["quiet", "symbolic", "half-remembered"],
}
traits = traits_bank.get(object_guess, traits_bank["personal relic"])
suffix = ["mote", "keeper", "echo", "sprite", "shade", "wisp"][seed % 6]
spirit_name = "".join(word.capitalize() for word in keywords_for_title(title)[:1]) + suffix.capitalize()
first_line = f"I am what remained when {object_guess} became a place."
if "airpods" in object_guess or object_guess == "earbuds":
first_line = "I carried private worlds through public noise."
elif object_guess == "book":
first_line = "I opened a galaxy small enough to fit in your hands."
elif object_guess == "monitor":
first_line = "I watched numbers become weather."
elif object_guess == "school bag":
first_line = "I carried the mornings you were not ready for."
elif object_guess == "phone":
first_line = "I kept private signals close enough to answer."
elif object_guess == "water bottle":
first_line = "I kept ordinary stamina within reach."
elif object_guess == "plush toy":
first_line = "I guarded sleep without needing to explain why."
elif object_guess == "game controller":
first_line = "I made faraway worlds answer through your hands."
elif object_guess == "sticker":
first_line = "I turned a surface into a signal."
elif object_guess == "shoes":
first_line = "I carried the route until it became routine."
elif object_guess == "painting":
first_line = "I guard the home that imagination returned to."
questions = [
"What do you remember?",
"Why are you in this hall?",
"What should visitors notice?",
]
responses = [
first_line,
f"The curator placed me in {hall} because this memory still has a shape.",
f"Look for the detail that does not shout: {memory_text[:110]}",
]
return {
"spirit_name": spirit_name,
"spirit_traits": traits,
"spirit_first_line": first_line,
"rules": [
"speak only from the object, memory, and artifact lore",
"do not behave like a generic assistant",
"answer as a quiet museum presence",
],
"sample_visitor_questions": questions,
"sample_spirit_responses": responses,
}
def resonance_links_for_artifact(hall: str, moods: list[str], plot: dict) -> list[dict]:
links = []
for art in nearby_artworks(plot):
shared = sorted(set(moods).intersection(art["moods"]))
links.append(
{
"title": art["title"],
"creator": art["player"],
"distance": art["distance"],
"resonance": "shared mood" if shared else "nearby museum placement",
"shared_moods": shared,
"hall_echo": hall,
}
)
return links
def passport_html(artifact: dict) -> str:
palette = artifact["palette"][:5]
swatches = "".join(
f""
for name, rgb in [next((block for block in BLOCKS if block[0] == item), ("stone", (120, 120, 120))) for item in palette]
)
coords = artifact["minecraft_coordinates"]
item = artifact.get("resource_pack_item", {})
share_url = artifact.get("share_url") or artifact_share_url(artifact)
share_fragment = artifact_passport_fragment(artifact)
qr_payload = artifact.get("qr_payload") or share_url
qr_src = qr_code_data_uri(qr_payload)
qr = (
f""
if qr_src
else qr_matrix_html(qr_payload)
)
preview_url = artifact_preview_url(artifact)
preview = (
f""
if preview_url
else f"
"""
def floor_map_html(artifact: dict) -> str:
cells = []
for z in range(CANVAS_SIZE):
for x in range(CANVAS_SIZE):
active = x == artifact["plot"]["x"] and z == artifact["plot"]["z"]
near = abs(x - artifact["plot"]["x"]) + abs(z - artifact["plot"]["z"]) <= 2
label = "X" if active else ""
cells.append(
f"{label}"
)
return f"""
Museum floor grid
Hover the grid to preview selection. The active plot pulses at {artifact['plot']['x']}, {artifact['plot']['z']}.
No downloads are needed to visit the demo world. Create a relic in the Space, join the server, then place the same relic at its lit plot.
World already built9 halls · 144 relic plots · living entry atlas
The Space map and Paper world share one coordinate contract: plot x,z becomes Minecraft XYZ using the same origin and 32-block plot size.
Server address{html_escape(MINECRAFT_JOIN_ADDRESS)}
{html_escape(MINECRAFT_JOIN_NOTE)}
1
Verify the world
Confirms the museum, living atlas, entry beacon, and plot grid are present.
/dreamwall museum check
2
See the object
/dreamwall pack
Loads the hosted object models.
3
Place the relic
/dreamwall import object | story | @owner
Paste the command generated after placing a relic in the Space.
Owner setup appendix
Only open this if you are installing on a host. The appendix contains the Paper plugin, hosted pack URL, optional prebuilt world, config, and SFTP helper. No panel password is included or stored.
Ready for Minecraft: paste this visitor command to generate the same object, light the route, engrave the name, and place the relic at plot {artifact['plot']['x']}, {artifact['plot']['z']}.
Visitor command{html_escape(import_command)}
No SFTP or downloads for visitors. The server owner installs the bridge once; every visitor can paste their own object/story/signature.
"""
for row in profile["upload_map"]
)
commands = "".join(f"{html_escape(command)}" for command in profile["first_run_commands"])
verify = "".join(f"{html_escape(command)}" for command in profile["verification_commands"])
return f"""
Server owner install card
{html_escape(clean_gallery_world)} / AfterBlock Museum
{CANVAS_SIZE * CANVAS_SIZE} plots
Space endpoint{html_escape(profile['space']['quick_curate_endpoint'])}Resource pack SHA1{html_escape(clean_pack_sha1)}Default relic{html_escape(clean_import_prompt)}
{html_escape(clean_import_story)}
{html_escape(clean_import_owner)}
Upload map
{upload_rows}
Run in Minecraft
{commands}
Verify
{verify}
Appendix ZIP below: it includes this card as afterblock-server-profile.json, plus UPLOAD_TO_SERVER.md and install-afterblock-paper.sh; it does not contain any server panel password or SFTP secret.
"""
def server_upload_guide_md(
clean_space_url: str,
clean_pack_url: str,
clean_pack_sha1: str,
clean_gallery_world: str,
) -> str:
return f"""# Upload AfterBlock to a Paper Server
This public kit is safe to share. It contains the Paper plugin, config, resource pack, demo proof files, and a helper script, but no server secret.
## Upload Map
```text
plugins/dreamwall-paper-bridge-0.1.0.jar -> plugins/dreamwall-paper-bridge-0.1.0.jar
plugins/DreamWall/config.yml -> plugins/DreamWall/config.yml
AfterBlockMuseum.zip -> AfterBlockMuseum.zip
afterblock-demo-world.zip -> afterblock-demo-world.zip
server.properties.append -> optional server.properties reference
```
The resource pack can also stay hosted here:
```text
{clean_pack_url}
sha1={clean_pack_sha1}
```
## File Manager Path
If your host has a web file manager, upload the files above, restart Paper, then run the Minecraft commands below as an op player.
## SFTP Helper
From this ZIP folder, run:
```bash
AFTERBLOCK_SFTP_HOST="{PEBBLEHOST_SFTP_HOST}" \\
AFTERBLOCK_SFTP_PORT="{PEBBLEHOST_SFTP_PORT}" \\
AFTERBLOCK_SFTP_USER="{PEBBLEHOST_SFTP_USER}" \\
sh install-afterblock-paper.sh
```
`AFTERBLOCK_SERVER_ROOT` is optional and defaults to `.`.
The SFTP password is typed only into the SFTP prompt from your host panel password. It is not stored in this kit.
## Minecraft First Run
```text
/dreamwall pack
/dreamwall museum check
/dreamwall import
```
Only run `/dreamwall museum build` if the prebuilt demo world was not installed.
Fast nearby proof:
```text
/dreamwall import here
```
Expected proof in `{clean_gallery_world}`:
```text
144 plot pads
YOU ARE HERE beacon
route compass from entry to the generated plot
engraved nameplate, passport lectern, profile button, and CustomModelData item
```
Space endpoint:
```text
{clean_space_url}/gradio_api/call/quick_curate
```
"""
def server_upload_helper_sh() -> str:
return """#!/usr/bin/env sh
set -eu
ROOT="${AFTERBLOCK_SERVER_ROOT:-.}"
HOST="${AFTERBLOCK_SFTP_HOST:-uk144.pebblehost.net}"
PORT="${AFTERBLOCK_SFTP_PORT:-2222}"
USER_NAME="${AFTERBLOCK_SFTP_USER:-itsarnavsalkade@gmail.com.5ea1f567}"
required_files="
plugins/dreamwall-paper-bridge-0.1.0.jar
plugins/DreamWall/config.yml
AfterBlockMuseum.zip
afterblock-demo-world.zip
afterblock-demo-proof.json
afterblock-server-profile.json
server.properties.append
"
echo "AfterBlock Paper server kit"
echo
missing=0
for file in $required_files; do
if [ ! -e "$file" ]; then
echo "Missing: $file"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
echo
echo "Run this script from the unzipped afterblock-paper-server-kit folder."
exit 1
fi
cat <<'MAP'
Upload map:
plugins/dreamwall-paper-bridge-0.1.0.jar -> plugins/dreamwall-paper-bridge-0.1.0.jar
plugins/DreamWall/config.yml -> plugins/DreamWall/config.yml
AfterBlockMuseum.zip -> AfterBlockMuseum.zip
afterblock-demo-world.zip -> afterblock-demo-world.zip
After restart, run in Minecraft:
/dreamwall pack
/dreamwall museum check
/dreamwall import
/dreamwall import here
Only run /dreamwall museum build if you did not install the prebuilt demo world.
MAP
if [ -z "$HOST" ] || [ -z "$USER_NAME" ]; then
cat <<'ENV'
No SFTP target was provided, so nothing was uploaded.
To override the prefilled PebbleHost target, set AFTERBLOCK_SFTP_HOST and AFTERBLOCK_SFTP_USER:
AFTERBLOCK_SFTP_HOST="other-sftp-host" AFTERBLOCK_SFTP_PORT="2222" AFTERBLOCK_SFTP_USER="other-sftp-user" sh install-afterblock-paper.sh
ENV
exit 0
fi
batch_file="${TMPDIR:-/tmp}/afterblock-sftp-batch.$$"
trap 'rm -f "$batch_file"' EXIT
cat > "$batch_file" < str:
config, clean_space_url, clean_pack_url, clean_pack_sha1, clean_gallery_world = server_config_yaml(
space_url,
resource_pack_url,
resource_pack_sha1,
gallery_world,
offer_pack_on_join,
import_prompt,
import_story,
import_owner,
)
clean_import_prompt = clean_text(import_prompt, DEFAULT_IMPORT_PROMPT)
clean_import_story = clean_text(import_story, DEFAULT_IMPORT_STORY)
clean_import_owner = clean_text(import_owner, DEFAULT_IMPORT_OWNER)
proof_manifest = server_demo_proof_manifest(
clean_space_url,
clean_pack_url,
clean_pack_sha1,
clean_gallery_world,
clean_import_prompt,
clean_import_story,
clean_import_owner,
)
owner_profile = server_owner_profile_manifest(
clean_space_url,
clean_pack_url,
clean_pack_sha1,
clean_gallery_world,
clean_import_prompt,
clean_import_story,
clean_import_owner,
)
kit_dir = tempfile.mkdtemp(prefix="afterblock-server-kit-")
zip_path = os.path.join(kit_dir, "afterblock-paper-server-kit.zip")
readme = f"""# AfterBlock Paper Server Kit
This kit configures a Paper server as a persistent AfterBlock Museum for:
{clean_space_url}
## Upload
This ZIP is self-contained for the server side:
1. Upload `plugins/dreamwall-paper-bridge-0.1.0.jar` to the server's `plugins/` folder.
2. Upload `AfterBlockMuseum.zip` to the server root, or use the hosted pack URL below.
3. Upload `plugins/DreamWall/config.yml` to the same path on the server.
4. Optional: unzip `afterblock-demo-world.zip` into the server root before restart for a prebuilt atlas-ready memory-spine world. It already contains the 144 plot pads, `YOU ARE HERE` beacon, and living entry atlas; run `/dreamwall museum build` later only if you want to refresh it.
5. Restart Paper.
## First Run
```text
/dreamwall pack
/dreamwall museum check
/dreamwall import
```
Only run `/dreamwall museum build` if the prebuilt demo world was not installed.
Use `/dreamwall import here` for a fast nearby proof during recording.
`/dreamwall import` uses the configured default relic:
```text
prompt={clean_import_prompt}
story={clean_import_story}
owner={clean_import_owner}
```
## Coordinate Contract
```text
world_x = {GALLERY_ORIGIN_X} + plot_x * {PLOT_SCALE}
world_y = {GALLERY_ORIGIN_Y}
world_z = {GALLERY_ORIGIN_Z} + plot_z * {PLOT_SCALE}
world = {clean_gallery_world}
```
## Resource Pack
```text
{clean_pack_url}
sha1={clean_pack_sha1}
```
## Included Files
```text
plugins/dreamwall-paper-bridge-0.1.0.jar
plugins/DreamWall/config.yml
AfterBlockMuseum.zip
afterblock-demo-world.zip
afterblock-demo-proof.json
afterblock-server-profile.json
UPLOAD_TO_SERVER.md
install-afterblock-paper.sh
server.properties.append
README.md
```
## Server Owner Profile
`afterblock-server-profile.json` is the shareable install card for a server owner. It includes the configured Space URL, world, resource pack, default relic, upload map, first-run commands, verification commands, and helper file names without storing any panel or SFTP secret.
## Upload Helper
`UPLOAD_TO_SERVER.md` gives the file-manager path and the exact first-run Minecraft commands.
`install-afterblock-paper.sh` is prefilled for the PebbleHost SFTP target `{PEBBLEHOST_SFTP_USER}@{PEBBLEHOST_SFTP_HOST}:{PEBBLEHOST_SFTP_PORT}` and prompts for the panel password at upload time. No password is stored.
## Demo Proof File
`afterblock-demo-proof.json` is included for judges and future installers. It records the Space endpoint, default relic, coordinate contract, expected `/dreamwall museum check` proof, resource-pack counts, checksums, and the one external blocker: PebbleHost upload still needs the panel/SFTP password.
## Checksums
```text
dreamwall-paper-bridge-0.1.0.jar sha1={PAPER_PLUGIN_SHA1}
dreamwall-paper-bridge-0.1.0.jar sha256={PAPER_PLUGIN_SHA256}
AfterBlockMuseum.zip sha1={clean_pack_sha1}
afterblock-demo-world.zip sha1={PREBUILT_WORLD_SHA1}
afterblock-demo-world.zip sha256={PREBUILT_WORLD_SHA256}
```
"""
server_properties = f"""# Optional server.properties lines
resource-pack={clean_pack_url}
resource-pack-sha1={clean_pack_sha1}
"""
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
zf.writestr("plugins/DreamWall/config.yml", config)
zf.writestr("README.md", readme)
zf.writestr("server.properties.append", server_properties)
zf.writestr(
"UPLOAD_TO_SERVER.md",
server_upload_guide_md(
clean_space_url,
clean_pack_url,
clean_pack_sha1,
clean_gallery_world,
),
)
zf.writestr("install-afterblock-paper.sh", server_upload_helper_sh())
zf.writestr("afterblock-demo-proof.json", json.dumps(proof_manifest, indent=2) + "\n")
zf.writestr("afterblock-server-profile.json", json.dumps(owner_profile, indent=2) + "\n")
if os.path.exists(PAPER_PLUGIN_JAR_PATH):
zf.write(PAPER_PLUGIN_JAR_PATH, "plugins/dreamwall-paper-bridge-0.1.0.jar")
if os.path.exists(RESOURCE_PACK_PATH):
zf.write(RESOURCE_PACK_PATH, "AfterBlockMuseum.zip")
if os.path.exists(PREBUILT_WORLD_PATH):
zf.write(PREBUILT_WORLD_PATH, "afterblock-demo-world.zip")
return zip_path
def server_config_bundle(
space_url: str = PUBLIC_SPACE_URL,
resource_pack_url: str = RESOURCE_PACK_URL,
resource_pack_sha1: str = RESOURCE_PACK_SHA1,
gallery_world: str = "world",
offer_pack_on_join: bool = False,
import_prompt: str = DEFAULT_IMPORT_PROMPT,
import_story: str = DEFAULT_IMPORT_STORY,
import_owner: str = DEFAULT_IMPORT_OWNER,
) -> tuple[str, str, str]:
return (
server_config_kit_text(
space_url,
resource_pack_url,
resource_pack_sha1,
gallery_world,
offer_pack_on_join,
import_prompt,
import_story,
import_owner,
),
server_config_zip(
space_url,
resource_pack_url,
resource_pack_sha1,
gallery_world,
offer_pack_on_join,
import_prompt,
import_story,
import_owner,
),
server_owner_install_card_html(
space_url,
resource_pack_url,
resource_pack_sha1,
gallery_world,
offer_pack_on_join,
import_prompt,
import_story,
import_owner,
),
)
def social_tag_for(owner_handle: str) -> str:
handle = (owner_handle or "").strip()
if handle and not handle.startswith("@"):
return f"@{handle}"
return handle
def visitor_identity(signature: str) -> tuple[str, str]:
raw = " ".join(str(signature or "").split())
if not raw:
return "Visitor", ""
handle = next((part for part in raw.split() if part.startswith("@") and len(part) > 1), "")
if handle:
owner_name = raw.replace(handle, "").strip() or handle[1:]
return owner_name, social_tag_for(handle)
return raw, ""
def compact_text(value: str, limit: int = 34) -> str:
value = " ".join(str(value or "").split())
if len(value) <= limit:
return value
return value[: max(0, limit - 1)].rstrip() + "…"
def demo_collection_artifact(index: int) -> dict:
base = OBJECT_LIBRARY[index % len(OBJECT_LIBRARY)]
name, input_kind, prompt, memory, object_kind = base
handle = SOCIAL_HANDLES[index % len(SOCIAL_HANDLES)]
variant = index // len(OBJECT_LIBRARY)
owner = handle.strip("@").replace("_", " ").title()
suffixes = ["launch", "rain", "exam", "midnight", "first-room"]
source_prompt = f"{prompt}, {suffixes[variant % len(suffixes)]} variant {variant + 1}"
memory_text = f"{memory} Visitor echo #{index + 1}."
artifact = build_museum_artifact(owner, handle, input_kind, source_prompt, memory_text)
artifact["catalog_index"] = index + 1
artifact["object_kind"] = object_kind
artifact["texture_path"] = f"assets/afterblock_textures/items/{object_kind.replace(' ', '_')}_{index + 1:04d}.png"
artifact["resource_pack_item"] = resource_pack_item_for(object_kind, index)
return artifact
def texture_key_for(object_guess: str) -> str:
key = object_guess.replace(" ", "_")
aliases = {
"prompted_painting": "painting",
"personal_relic": "notebook",
"animal_spirit": "toy_car",
"school_bag": "school_bag",
"water_bottle": "water_bottle",
"usb_drive": "usb_drive",
"phone": "phone",
"plush_toy": "plush_toy",
"sticker": "sticker",
"shoes": "shoes",
"controller": "game_controller",
"xbox_controller": "game_controller",
}
return aliases.get(key, key)
def resource_manifest() -> list[dict]:
path = "assets/afterblock_textures/afterblock_manifest.json"
try:
with open(path, "r", encoding="utf-8") as handle:
return json.load(handle)
except OSError:
return []
def resource_pack_stats() -> dict:
manifest = resource_manifest()
kinds = {item.get("kind", "unknown") for item in manifest}
shapes = {item.get("shape", "unknown") for item in manifest}
profiles = {item.get("model_profile", "legacy") for item in manifest}
materials = {item.get("material", "untextured") for item in manifest}
return {
"total_items": len(manifest),
"total_kinds": len(kinds),
"total_shapes": len(shapes),
"total_model_profiles": len(profiles),
"total_materials": len(materials),
"page_count": max(1, math.ceil(len(manifest) / TEXTURE_PAGE_SIZE)),
}
def resource_pack_item_for(object_guess: str, seed_hint: int = 0) -> dict:
key = texture_key_for(object_guess)
manifest = resource_manifest()
matches = [item for item in manifest if item.get("kind") == key]
if not matches:
matches = [item for item in manifest if item.get("kind") == "notebook"] or manifest
if not matches:
return {
"id": "missing",
"custom_model_data": 0,
"texture": "",
"model": "",
"recommended_item": "minecraft:paper",
}
return matches[seed_hint % len(matches)]
def texture_kind_choices() -> list[str]:
kinds = sorted({item.get("kind", "unknown") for item in resource_manifest()})
return ["all"] + kinds
def texture_preview_url(item_id: str) -> str:
base = os.getenv(
"TEXTURE_PREVIEW_BASE_URL",
"https://raw.githubusercontent.com/Arnie016/dreamwall-mc/main/assets/afterblock_textures/gallery/previews",
).rstrip("/")
if base:
return f"{base}/{item_id}.png"
return f"assets/afterblock_textures/gallery/previews/{item_id}.png"
def block_rgb(block_name: str) -> tuple[int, int, int]:
return next((rgb for name, rgb in BLOCKS if name == block_name), (180, 156, 104))
def hex_color(rgb: tuple[int, int, int]) -> str:
return "#{:02x}{:02x}{:02x}".format(*rgb)
def artifact_model_html(artifact: dict) -> str:
item = artifact.get("resource_pack_item", {})
item_id = item.get("id", "missing")
hall_rgb = hall_color(artifact["hall"])
palette = [hex_color(block_rgb(name)) for name in artifact.get("palette", [])[:5]]
if not palette:
palette = [hex_color(hall_rgb), "#e8c56f", "#3b3120"]
payload = {
"title": artifact["title"],
"owner": owner_display(artifact),
"hall": artifact["hall"],
"when": artifact.get("artifact_when", artifact["placement_reason"]),
"coordinates": artifact["minecraft_coordinates"],
"custom_model_data": item.get("custom_model_data", 0),
"model": item.get("model", "afterblock:item/missing"),
"kind": item.get("kind", texture_key_for(artifact["object_guess"])),
"object_guess": artifact["object_guess"],
"shape": item.get("shape", artifact["object_guess"]),
"material": item.get("material", "museum material"),
"finish": item.get("finish", "artifact finish"),
"profile": item.get("model_profile", "fixed pedestal"),
"orientation": item.get("orientation", "front-facing"),
"element_count": int(item.get("element_count") or 7),
"preview_url": texture_preview_url(item_id) if item_id != "missing" else "",
"colors": palette,
"hall_color": hex_color(hall_rgb),
"story_caption": artifact.get("memory_text", artifact.get("plaque_line", "")),
"social_tag": artifact.get("social_tag", owner_display(artifact)),
"give_command": f"/give @p minecraft:paper[minecraft:custom_model_data={item.get('custom_model_data', 0)}] 1",
}
payload_json = json.dumps(payload)
fallback_swatches = "".join(
f"" for color in payload["colors"]
)
srcdoc = f"""
The banner follows the selected relic through its hall, plot, item code, and passport route.
You are here{html_escape(selected['hall'])} · {html_escape(hall_routes.get(selected['hall'], selected['zone']))}
{html_escape(selected['title'])} is installed at plot {selected['plot']['x']}, {selected['plot']['z']} with CMD {selected_item.get('custom_model_data', 0)}.
Type one object, attach the story people should know, then place it as one persistent Minecraft relic.
One promptStory captionMinecraft item-code resource pack
"""
)
gr.HTML('')
with gr.Tabs():
with gr.Tab("Place in Museum"):
with gr.Row(elem_classes=["main-museum-layout"]):
with gr.Column(scale=4):
with gr.Group(elem_classes=["museum-kiosk", "prompt-panel"]):
gr.HTML("
Place in the museum
")
quick_prompt = gr.Textbox(
label="Museum prompt",
placeholder="Example: blue school bag from exam week",
lines=2,
value=DEFAULT_MUSEUM_PROMPT,
)
quick_spirit = gr.Textbox(
label="Story caption",
placeholder="What should visitors know when they open this relic?",
lines=3,
value=DEFAULT_STORY_CAPTION,
)
quick_handle = gr.Textbox(
label="Visitor signature",
placeholder="@handle or display name",
lines=1,
value="@Wildstash",
)
quick_image = gr.Image(
label="Optional image reference",
type="filepath",
sources=["upload", "clipboard"],
height=96,
elem_classes=["photo-drop-compact"],
)
quick_button = gr.Button("Place in Museum", variant="primary")
with gr.Accordion("Minecraft proof and owner files", open=False):
museum_command = gr.Textbox(
value=INITIAL_MUSEUM_OUTPUTS[1],
label="Item command",
lines=2,
max_lines=2,
show_copy_button=True,
elem_classes=["command-copy"],
)
museum_packet = gr.Textbox(
value=INITIAL_MUSEUM_OUTPUTS[5],
label="Technical appendix: dreamwall.museum.v1",
lines=12,
max_lines=18,
show_copy_button=True,
)
museum_relic_server_download = gr.File(
value=INITIAL_MUSEUM_OUTPUTS[11],
label="Owner appendix for this relic",
)
with gr.Column(scale=6):
museum_model = gr.HTML(value=INITIAL_MUSEUM_OUTPUTS[0], label="Artifact model")
museum_coordinates = gr.HTML(value=INITIAL_MUSEUM_OUTPUTS[2], label="Coordinates")
museum_server_handoff = gr.HTML(
value=INITIAL_MUSEUM_OUTPUTS[12],
label="Run in Minecraft",
)
museum_wall = gr.HTML(value=INITIAL_MUSEUM_OUTPUTS[9], label="Persistent museum")
with gr.Accordion("Blueprint and catalog", open=False):
with gr.Row():
with gr.Column(scale=5):
with gr.Group(elem_classes=["museum-stage"]):
museum_preview = gr.Image(value=INITIAL_MUSEUM_OUTPUTS[6], label="Museum map", type="filepath", height=420)
with gr.Column(scale=5):
museum_catalog = gr.Dataframe(
value=INITIAL_MUSEUM_OUTPUTS[7],
headers=["#", "creator", "title", "object", "hall", "score", "xyz", "cmd"],
label="Generated relic catalog",
row_count=8,
col_count=(8, "fixed"),
interactive=False,
)
with gr.Tab("Object Atlas"):
gr.HTML(
"""
Object Atlas
A living field guide for every Minecraft relic look. Pick a kind, flip pages, then open item codes only when you need them.
Use this only when installing on PebbleHost or another Paper server. The demo itself should show /dreamwall pack, /dreamwall museum check, and /dreamwall import.