Register the Opus 4.8 mythos-craft run (Claude Code, Max)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
664a7115c7
commit
42efab56e2
4 files changed
+384
-2
No files matched your search
+1
-1
@@ -18,7 +18,7 @@ One-shot LLM eval cases by NAGI STUDIO: same prompt, different model x harness x
|
||||
|---|---|---|---|---|
|
||||
| Claude Fable 5 | Anthropic | Claude Web App | Max | 02 |
|
||||
| Claude Fable 5 | Anthropic | Claude Code | Max | 02 |
|
||||
| Claude Opus 4.8 | Anthropic | Claude Code | Max | 01 |
|
||||
| Claude Opus 4.8 | Anthropic | Claude Code | Max | 02 |
|
||||
| GPT-5.5 Pro | OpenAI | ChatGPT Web | Extended Pro | 02 |
|
||||
| GPT-5.5 | OpenAI | Codex CLI (no skills) | xhigh | 02 |
|
||||
| Gemini 3.1 Pro | Google | AntiGravity | High | 02 |
|
||||
|
||||
@@ -18,7 +18,7 @@ NAGI STUDIO 的 LLM 测评案例集:同一段提示词,不同「模型 × Ha
|
||||
|---|---|---|---|---|
|
||||
| Claude Fable 5 | Anthropic | Claude 网页版 | Max | 02 |
|
||||
| Claude Fable 5 | Anthropic | Claude Code | Max | 02 |
|
||||
| Claude Opus 4.8 | Anthropic | Claude Code | Max | 01 |
|
||||
| Claude Opus 4.8 | Anthropic | Claude Code | Max | 02 |
|
||||
| GPT-5.5 Pro | OpenAI | ChatGPT 网页版 | Extended Pro | 02 |
|
||||
| GPT-5.5 | OpenAI | Codex CLI(无 skill) | xhigh | 02 |
|
||||
| Gemini 3.1 Pro | Google | AntiGravity | High | 02 |
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
"vendor": "Anthropic",
|
||||
"order": 15,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.8(Max 思考配额)一次生成,本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.8 (Max effort) inside Claude Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.8(Max 思考配额)一次生成。",
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>方块秘境 · Voxel Mythos</title>
|
||||
<style>
|
||||
:root { --acid:#c8ff00; --night:#0c0e14; --ink:#e9f0ff; }
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
html, body { width:100%; height:100%; overflow:hidden; background:#0c0e14; }
|
||||
body { font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; color:var(--ink); -webkit-user-select:none; user-select:none; touch-action:none; }
|
||||
canvas { display:block; }
|
||||
#game { position:fixed; inset:0; }
|
||||
|
||||
/* Crosshair */
|
||||
#cross { position:fixed; left:50%; top:50%; width:22px; height:22px; transform:translate(-50%,-50%); pointer-events:none; z-index:20; mix-blend-mode:difference; opacity:.9; }
|
||||
#cross::before, #cross::after { content:""; position:absolute; background:#fff; }
|
||||
#cross::before { left:50%; top:0; width:2px; height:100%; transform:translateX(-50%); }
|
||||
#cross::after { top:50%; left:0; height:2px; width:100%; transform:translateY(-50%); }
|
||||
|
||||
/* HUD top-left */
|
||||
#hud { position:fixed; left:14px; top:12px; z-index:20; font-size:12px; line-height:1.55; text-shadow:0 1px 2px rgba(0,0,0,.8); pointer-events:none; }
|
||||
#hud b { color:var(--acid); font-weight:700; }
|
||||
#hud .dim { color:#9aa6c2; }
|
||||
|
||||
/* Hotbar */
|
||||
#hotbar { position:fixed; left:50%; bottom:18px; transform:translateX(-50%); z-index:20; display:flex; gap:6px; padding:6px; background:rgba(8,10,18,.55); border:1px solid rgba(255,255,255,.12); border-radius:12px; backdrop-filter:blur(8px); pointer-events:none; }
|
||||
.slot { position:relative; width:48px; height:48px; border-radius:8px; background:rgba(255,255,255,.04); border:2px solid rgba(255,255,255,.10); display:flex; align-items:center; justify-content:center; }
|
||||
.slot.active { border-color:var(--acid); box-shadow:0 0 0 2px rgba(200,255,0,.25), 0 6px 20px rgba(200,255,0,.18); transform:translateY(-3px); }
|
||||
.slot canvas { width:34px; height:34px; image-rendering:pixelated; border-radius:4px; }
|
||||
.slot .key { position:absolute; top:-9px; left:-6px; font-size:9px; color:#0c0e14; background:var(--acid); border-radius:5px; padding:0 4px; font-weight:800; }
|
||||
.slot .nm { position:absolute; bottom:-16px; left:50%; transform:translateX(-50%); font-size:9px; color:#cfd8ef; white-space:nowrap; opacity:0; transition:opacity .15s; }
|
||||
.slot.active .nm { opacity:1; }
|
||||
|
||||
/* Start / pause overlay */
|
||||
#veil { position:fixed; inset:0; z-index:40; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:22px;
|
||||
background:radial-gradient(1200px 800px at 50% 20%, rgba(40,60,120,.35), rgba(8,10,18,.92) 60%), #06070c; text-align:center; transition:opacity .4s; }
|
||||
#veil.hide { opacity:0; pointer-events:none; }
|
||||
#veil .badge { font-size:11px; letter-spacing:.4em; color:var(--acid); text-transform:uppercase; }
|
||||
#veil h1 { font-size:clamp(34px, 7vw, 76px); line-height:.96; font-weight:800; letter-spacing:-.02em;
|
||||
background:linear-gradient(180deg,#fff, #9fb6ff 70%, #6f86ff); -webkit-background-clip:text; background-clip:text; color:transparent; text-shadow:0 0 60px rgba(120,150,255,.25); }
|
||||
#veil h1 small { display:block; font-size:.34em; letter-spacing:.5em; color:#7d8bb8; -webkit-text-fill-color:#7d8bb8; margin-top:10px; font-weight:600; }
|
||||
#veil .play { pointer-events:auto; cursor:pointer; font:inherit; font-weight:800; font-size:15px; letter-spacing:.18em; color:#0c0e14; background:var(--acid); border:none; padding:14px 34px; border-radius:999px; text-transform:uppercase; box-shadow:0 14px 40px rgba(200,255,0,.30); transition:transform .15s, box-shadow .15s; }
|
||||
#veil .play:hover { transform:translateY(-2px); box-shadow:0 18px 50px rgba(200,255,0,.45); }
|
||||
#veil .play:disabled { background:#39414f; color:#7d8bb8; box-shadow:none; cursor:default; }
|
||||
#veil .keys { display:flex; gap:18px; flex-wrap:wrap; justify-content:center; max-width:680px; font-size:12px; color:#aab4d0; }
|
||||
#veil .keys span b { color:#fff; background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.16); border-radius:6px; padding:2px 7px; margin-right:5px; font-weight:700; }
|
||||
#veil .tip { font-size:11px; color:#6b769a; max-width:560px; line-height:1.7; }
|
||||
|
||||
#bar { position:fixed; left:50%; bottom:-2px; transform:translateX(-50%); width:min(420px,70vw); height:3px; background:rgba(255,255,255,.08); z-index:41; border-radius:2px; overflow:hidden; }
|
||||
#bar i { display:block; height:100%; width:0%; background:var(--acid); transition:width .2s; }
|
||||
|
||||
#flash { position:fixed; inset:0; z-index:15; pointer-events:none; background:#fff; opacity:0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game"></canvas>
|
||||
<div id="cross"></div>
|
||||
<div id="flash"></div>
|
||||
|
||||
<div id="hud">
|
||||
<div><b>方块秘境</b> <span class="dim">Voxel Mythos</span></div>
|
||||
<div class="dim" id="stat">pos —</div>
|
||||
<div class="dim" id="biome"></div>
|
||||
<div class="dim" id="fps"></div>
|
||||
</div>
|
||||
|
||||
<div id="hotbar"></div>
|
||||
|
||||
<div id="veil">
|
||||
<div class="badge">A Hand-built Myth · 神话造物</div>
|
||||
<h1>方块秘境<small>VOXEL MYTHOS</small></h1>
|
||||
<button class="play" id="play" disabled>生成世界中…</button>
|
||||
<div class="keys">
|
||||
<span><b>W A S D</b>移动</span>
|
||||
<span><b>SPACE</b>跳跃</span>
|
||||
<span><b>SHIFT</b>疾跑</span>
|
||||
<span><b>鼠标</b>环视</span>
|
||||
<span><b>左键</b>破坏</span>
|
||||
<span><b>右键</b>放置</span>
|
||||
<span><b>1-9 / 滚轮</b>切换方块</span>
|
||||
<span><b>F</b>飞行</span>
|
||||
</div>
|
||||
<div class="tip">柏林噪声生成的无限地形之上,矗立着由开发者亲手设计的「天悬神殿」与「世界之树」——光之回廊、水晶尖塔、悬浮岛屿、巨龙守卫与符文法阵环绕其间。降临神域,开始你的造物。</div>
|
||||
</div>
|
||||
<div id="bar"><i id="barfill"></i></div>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
||||
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
|
||||
}}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
|
||||
|
||||
// ======================================================================
|
||||
// 方块秘境 · VOXEL MYTHOS — single-file voxel sandbox
|
||||
// Perlin terrain · first-person · break/place · a hand-designed mythos
|
||||
// ======================================================================
|
||||
|
||||
// ---------- tunables ----------
|
||||
const CHUNK = 16; // chunk width/depth in blocks
|
||||
const WORLD_H = 72; // world height
|
||||
const SEA = 22; // sea level
|
||||
const RENDER_DIST = 6; // chunk radius around player to keep loaded
|
||||
const SEED = 1337;
|
||||
|
||||
// ---------- block ids ----------
|
||||
const B = {
|
||||
AIR:0, GRASS:1, DIRT:2, STONE:3, SAND:4, LOG:5, LEAVES:6, WATER:7,
|
||||
PLANK:8, COBBLE:9, MARBLE:10, GLOW:11, CRYSTAL:12, GOLD:13, OBSID:14,
|
||||
RUNE:15, SNOW:16, MOSS:17, LAPIS:18, BEDROCK:19
|
||||
};
|
||||
// transparency / pass classification
|
||||
const TRANSPARENT = new Set([B.AIR, B.WATER, B.LEAVES, B.CRYSTAL]); // don't fully occlude neighbours
|
||||
const EMISSIVE = new Set([B.GLOW, B.RUNE, B.LAPIS]); // full-bright pass
|
||||
const GLASSY = new Set([B.WATER, B.CRYSTAL]); // translucent pass
|
||||
const isAir = id => id === B.AIR;
|
||||
const isOpaque = id => id !== B.AIR && !TRANSPARENT.has(id);
|
||||
const isSolid = id => id !== B.AIR && id !== B.WATER; // for collision
|
||||
|
||||
// ---------- seeded PRNG ----------
|
||||
function mulberry32(a){ return function(){ a|=0; a=a+0x6D2B79F5|0; let t=Math.imul(a^a>>>15,1|a); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }; }
|
||||
function hash2(x,y){ let h = x*374761393 + y*668265263; h = (h^(h>>>13))*1274126177; return ((h^(h>>>16))>>>0)/4294967296; }
|
||||
|
||||
// ---------- Perlin noise (improved, seeded permutation) ----------
|
||||
const perm = new Uint8Array(512);
|
||||
(function buildPerm(){
|
||||
const p = new Uint8Array(256); for(let i=0;i<256;i++) p[i]=i;
|
||||
const rng = mulberry32(SEED);
|
||||
for(let i=255;i>0;i--){ const j=(rng()*(i+1))|0; const t=p[i]; p[i]=p[j]; p[j]=t; }
|
||||
for(let i=0;i<512;i++) perm[i]=p[i&255];
|
||||
})();
|
||||
const fade = t => t*t*t*(t*(t*6-15)+10);
|
||||
const lerp = (a,b,t) => a+(b-a)*t;
|
||||
function grad2(h,x,y){ switch(h&7){ case 0:return x+y; case 1:return -x+y; case 2:return x-y; case 3:return -x-y; case 4:return x; case 5:return -x; case 6:return y; default:return -y; } }
|
||||
function perlin2(x,y){
|
||||
const X=Math.floor(x)&255, Y=Math.floor(y)&255; x-=Math.floor(x); y-=Math.floor(y);
|
||||
const u=fade(x), v=fade(y);
|
||||
const aa=perm[perm[X]+Y], ab=perm[perm[X]+Y+1], ba=perm[perm[X+1]+Y], bb=perm[perm[X+1]+Y+1];
|
||||
return lerp( lerp(grad2(aa,x,y), grad2(ba,x-1,y), u), lerp(grad2(ab,x,y-1), grad2(bb,x-1,y-1), u), v );
|
||||
}
|
||||
// fractal brownian motion
|
||||
function fbm(x,y,oct,lac,gain){ let f=0,amp=0.5,freq=1,norm=0; for(let i=0;i<oct;i++){ f+=amp*perlin2(x*freq,y*freq); norm+=amp; amp*=gain; freq*=lac; } return f/norm; }
|
||||
|
||||
// ======================================================================
|
||||
// PROCEDURAL TEXTURE ATLAS — drawn pixel-by-pixel, NearestFilter
|
||||
// ======================================================================
|
||||
const TILE = 16, COLS = 8;
|
||||
// tile indices
|
||||
const T = { GRASS_TOP:0, GRASS_SIDE:1, DIRT:2, STONE:3, SAND:4, LOG_TOP:5, LOG_SIDE:6, LEAVES:7,
|
||||
WATER:8, PLANK:9, COBBLE:10, MARBLE:11, GLOW:12, CRYSTAL:13, GOLD:14, OBSID:15,
|
||||
RUNE:16, SNOW:17, MOSS:18, LAPIS:19, BEDROCK:20, MARBLE_GOLD:21 };
|
||||
const TILE_COUNT = 22, ROWS = Math.ceil(TILE_COUNT/COLS);
|
||||
const atlas = document.createElement('canvas'); atlas.width = COLS*TILE; atlas.height = ROWS*TILE;
|
||||
const actx = atlas.getContext('2d');
|
||||
|
||||
// helpers operating on a TILE×TILE off-screen, then blitted into atlas at index
|
||||
function makeTile(index, draw){
|
||||
const c = document.createElement('canvas'); c.width=TILE; c.height=TILE; const g=c.getContext('2d');
|
||||
const img = g.createImageData(TILE,TILE); const d = img.data;
|
||||
const put=(x,y,r,gr,b,a=255)=>{ if(x<0||y<0||x>=TILE||y>=TILE)return; const i=(y*TILE+x)*4; d[i]=r;d[i+1]=gr;d[i+2]=b;d[i+3]=a; };
|
||||
draw(put, mulberry32(SEED*131 + index*7919));
|
||||
g.putImageData(img,0,0);
|
||||
const cx=(index%COLS)*TILE, cy=((index/COLS)|0)*TILE;
|
||||
actx.drawImage(c, cx, cy);
|
||||
}
|
||||
// colour util
|
||||
function vary(base, rng, amt){ const k=(rng()*2-1)*amt; return [clamp8(base[0]+k), clamp8(base[1]+k), clamp8(base[2]+k)]; }
|
||||
function clamp8(v){ return v<0?0:v>255?255:v|0; }
|
||||
function mix(a,b,t){ return [a[0]+(b[0]-a[0])*t, a[1]+(b[1]-a[1])*t, a[2]+(b[2]-a[2])*t]; }
|
||||
|
||||
// ---- individual tile painters ----
|
||||
function fill(put, base, rng, amt){ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const c=vary(base,rng,amt); put(x,y,c[0],c[1],c[2]); } }
|
||||
|
||||
makeTile(T.DIRT, (put,rng)=>{ fill(put,[134,96,67],rng,26);
|
||||
for(let i=0;i<10;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=vary([96,68,46],rng,18); put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.GRASS_TOP, (put,rng)=>{ fill(put,[86,150,58],rng,24);
|
||||
for(let i=0;i<22;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=rng()<.5?[64,118,42]:[110,176,70]; put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.GRASS_SIDE, (put,rng)=>{ // dirt body + green crown w/ dribble
|
||||
fill(put,[134,96,67],rng,24);
|
||||
for(let x=0;x<TILE;x++){ const top=2+((rng()*3)|0);
|
||||
for(let y=0;y<top;y++){ const c=vary([78,142,52],rng,20); put(x,y,c[0],c[1],c[2]); }
|
||||
if(rng()<.4){ const c=vary([70,128,46],rng,16); put(x,top,c[0],c[1],c[2]); if(rng()<.4) put(x,top+1,c[0]-8,c[1]-8,c[2]-6); } } });
|
||||
|
||||
makeTile(T.STONE, (put,rng)=>{ fill(put,[128,128,133],rng,16);
|
||||
for(let i=0;i<6;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,96,96,102); } });
|
||||
|
||||
makeTile(T.COBBLE, (put,rng)=>{ const cell=4; for(let cy=0;cy<TILE;cy+=cell)for(let cx=0;cx<TILE;cx+=cell){ const b=110+((rng()*40)|0);
|
||||
for(let y=0;y<cell;y++)for(let x=0;x<cell;x++){ const edge=(x===0||y===0); const v=edge?70:b+((rng()*16-8)|0); put(cx+x,cy+y,v,v,v+4); } } });
|
||||
|
||||
makeTile(T.SAND, (put,rng)=>{ fill(put,[219,203,140],rng,16);
|
||||
for(let i=0;i<8;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,198,182,120); } });
|
||||
|
||||
makeTile(T.SNOW, (put,rng)=>{ fill(put,[236,242,250],rng,8);
|
||||
for(let i=0;i<6;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,255,255,255); } });
|
||||
|
||||
makeTile(T.LOG_SIDE, (put,rng)=>{ for(let x=0;x<TILE;x++){ const streak=(x%4===0)?-18:0;
|
||||
for(let y=0;y<TILE;y++){ const c=vary([101,73,42],rng,12); put(x,y,c[0]+streak,c[1]+streak,c[2]+streak); } }
|
||||
for(let i=0;i<6;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,70,50,28); } });
|
||||
|
||||
makeTile(T.LOG_TOP, (put,rng)=>{ const c0=TILE/2-0.5; for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const d=Math.hypot(x-c0,y-c0); const ring=(Math.floor(d)%2===0); const base=ring?[176,140,95]:[150,115,75]; const c=vary(base,rng,10); put(x,y,c[0],c[1],c[2]); }
|
||||
put(c0|0,c0|0,120,92,58); });
|
||||
|
||||
makeTile(T.LEAVES, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ if(rng()<.10){ put(x,y,0,0,0,0); continue; } const r=rng(); const base=r<.4?[38,82,28]:r<.8?[54,110,40]:[78,140,52]; const c=vary(base,rng,12); put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.MOSS, (put,rng)=>{ fill(put,[120,120,124],rng,14);
|
||||
for(let i=0;i<60;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; if(rng()<.5){ const c=vary([62,116,46],rng,18); put(x,y,c[0],c[1],c[2]); } } });
|
||||
|
||||
makeTile(T.WATER, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const w=Math.sin((x+y)*0.9)*0.5+0.5; const base=mix([34,104,196],[60,140,226],w*0.6+rng()*0.2); put(x,y,base[0]|0,base[1]|0,base[2]|0,210); } });
|
||||
|
||||
makeTile(T.PLANK, (put,rng)=>{ fill(put,[176,134,78],rng,14);
|
||||
for(let y=0;y<TILE;y++){ if(y%4===0){ for(let x=0;x<TILE;x++) put(x,y,120,86,46); } }
|
||||
for(let i=0;i<10;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,150,112,62); } });
|
||||
|
||||
makeTile(T.MARBLE, (put,rng)=>{ fill(put,[232,230,223],rng,7);
|
||||
let vx=(rng()*TILE)|0; for(let y=0;y<TILE;y++){ vx+=(rng()*3-1)|0; const c=[188,190,198]; put((vx+TILE)%TILE,y,c[0],c[1],c[2]); }
|
||||
let vx2=(rng()*TILE)|0; for(let y=0;y<TILE;y++){ vx2+=(rng()*3-1)|0; put((vx2+TILE)%TILE,y,200,202,210); } });
|
||||
|
||||
makeTile(T.MARBLE_GOLD, (put,rng)=>{ fill(put,[232,230,223],rng,7);
|
||||
for(let x=0;x<TILE;x++){ put(x,0,255,210,74); put(x,TILE-1,205,160,40); put(x,1,255,236,150); } });
|
||||
|
||||
makeTile(T.GLOW, (put,rng)=>{ fill(put,[255,206,107],rng,14);
|
||||
for(let i=0;i<26;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,255,238,176); }
|
||||
for(let i=0;i<8;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; put(x,y,224,150,60); } });
|
||||
|
||||
makeTile(T.CRYSTAL, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const band=((x+y)>>1)%3; const base=band===0?[210,250,255]:band===1?[150,235,255]:[96,180,228]; const c=vary(base,rng,10); put(x,y,c[0],c[1],c[2],235); }
|
||||
for(let i=0;i<5;i++){ const x=(rng()*TILE)|0; put(x,(rng()*TILE)|0,255,255,255,255); } });
|
||||
|
||||
makeTile(T.LAPIS, (put,rng)=>{ fill(put,[40,66,176],rng,16);
|
||||
for(let i=0;i<26;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=rng()<.5?[92,124,232]:[24,40,120]; put(x,y,c[0],c[1],c[2]); } });
|
||||
|
||||
makeTile(T.GOLD, (put,rng)=>{ const half=TILE/2; for(let by=0;by<2;by++)for(let bx=0;bx<2;bx++){ for(let y=0;y<half;y++)for(let x=0;x<half;x++){ let c=[255,210,74]; if(x===0||y===0) c=[150,112,30]; else if(x<2&&y<2) c=[255,244,180]; else if(x>half-3&&y>half-3) c=[205,160,40]; const v=vary(c,rng,8); put(bx*half+x,by*half+y,v[0],v[1],v[2]); } } });
|
||||
|
||||
makeTile(T.OBSID, (put,rng)=>{ fill(put,[22,18,34],rng,8);
|
||||
for(let i=0;i<14;i++){ const x=(rng()*TILE)|0,y=(rng()*TILE)|0; const c=vary([74,44,98],rng,18); put(x,y,c[0],c[1],c[2]); }
|
||||
for(let k=0;k<TILE;k++){ if(k%5===0) put(k,k,120,90,150); } });
|
||||
|
||||
makeTile(T.BEDROCK, (put,rng)=>{ for(let y=0;y<TILE;y++)for(let x=0;x<TILE;x++){ const v=30+((rng()*60)|0); put(x,y,v,v,v+3); } });
|
||||
|
||||
makeTile(T.RUNE, (put,rng)=>{ fill(put,[58,58,66],rng,12); // dark stone + glowing glyph
|
||||
const glyph=[ "0001111000","0010000100","0100110010","0101001010","1010000101","1010000101","0101001010","0100110010","0010000100","0001111000" ];
|
||||
const off=3; for(let y=0;y<10;y++)for(let x=0;x<10;x++){ if(glyph[y][x]==='1'){ const c=vary([120,240,255],rng,16); put(x+off,y+off,c[0],c[1],c[2]); } }
|
||||
put(8,8,200,250,255); });
|
||||
|
||||
// ---------- atlas → texture ----------
|
||||
const atlasTex = new THREE.CanvasTexture(atlas);
|
||||
atlasTex.magFilter = THREE.NearestFilter; atlasTex.minFilter = THREE.NearestFilter;
|
||||
atlasTex.generateMipmaps = false; atlasTex.flipY = false; // canvas (0,0)=top-left maps to UV(0,0)
|
||||
atlasTex.colorSpace = THREE.SRGBColorSpace; atlasTex.anisotropy = 1;
|
||||
|
||||
const EPS = 0.0008; // inset to kill texture bleed
|
||||
function tileUV(idx){ const col=idx%COLS, row=(idx/COLS)|0;
|
||||
const u0=(col*TILE)/atlas.width + EPS, v0=(row*TILE)/atlas.height + EPS;
|
||||
const u1=((col+1)*TILE)/atlas.width - EPS, v1=((row+1)*TILE)/atlas.height - EPS;
|
||||
return [u0,v0,u1,v1]; // v0=top edge, v1=bottom edge (flipY=false)
|
||||
}
|
||||
|
||||
// block id → face tiles {top, side, bottom}
|
||||
const FACE = {};
|
||||
FACE[B.GRASS]={top:T.GRASS_TOP, side:T.GRASS_SIDE, bottom:T.DIRT};
|
||||
FACE[B.DIRT]={top:T.DIRT,side:T.DIRT,bottom:T.DIRT};
|
||||
FACE[B.STONE]={top:T.STONE,side:T.STONE,bottom:T.STONE};
|
||||
FACE[B.SAND]={top:T.SAND,side:T.SAND,bottom:T.SAND};
|
||||
FACE[B.LOG]={top:T.LOG_TOP,side:T.LOG_SIDE,bottom:T.LOG_TOP};
|
||||
FACE[B.LEAVES]={top:T.LEAVES,side:T.LEAVES,bottom:T.LEAVES};
|
||||
FACE[B.WATER]={top:T.WATER,side:T.WATER,bottom:T.WATER};
|
||||
FACE[B.PLANK]={top:T.PLANK,side:T.PLANK,bottom:T.PLANK};
|
||||
FACE[B.COBBLE]={top:T.COBBLE,side:T.COBBLE,bottom:T.COBBLE};
|
||||
FACE[B.MARBLE]={top:T.MARBLE,side:T.MARBLE_GOLD,bottom:T.MARBLE};
|
||||
FACE[B.GLOW]={top:T.GLOW,side:T.GLOW,bottom:T.GLOW};
|
||||
FACE[B.CRYSTAL]={top:T.CRYSTAL,side:T.CRYSTAL,bottom:T.CRYSTAL};
|
||||
FACE[B.GOLD]={top:T.GOLD,side:T.GOLD,bottom:T.GOLD};
|
||||
FACE[B.OBSID]={top:T.OBSID,side:T.OBSID,bottom:T.OBSID};
|
||||
FACE[B.RUNE]={top:T.RUNE,side:T.OBSID,bottom:T.OBSID};
|
||||
FACE[B.SNOW]={top:T.SNOW,side:T.SNOW,bottom:T.SNOW};
|
||||
FACE[B.MOSS]={top:T.MOSS,side:T.MOSS,bottom:T.MOSS};
|
||||
FACE[B.LAPIS]={top:T.LAPIS,side:T.LAPIS,bottom:T.LAPIS};
|
||||
FACE[B.BEDROCK]={top:T.BEDROCK,side:T.BEDROCK,bottom:T.BEDROCK};
|
||||
|
||||
// ---------- materials ----------
|
||||
const matOpaque = new THREE.MeshLambertMaterial({ map:atlasTex, vertexColors:true, alphaTest:0.5, side:THREE.FrontSide });
|
||||
const matGlass = new THREE.MeshLambertMaterial({ map:atlasTex, vertexColors:true, transparent:true, opacity:0.82, depthWrite:false, side:THREE.DoubleSide });
|
||||
const matGlow = new THREE.MeshBasicMaterial({ map:atlasTex, vertexColors:true, transparent:true, alphaTest:0.4 });
|
||||
|
||||
// ======================================================================
|
||||
// WORLD MODEL
|
||||
// - chunks store generated voxel data
|
||||
// - overrides[] holds everything non-terrain: trees, the mythos, player edits
|
||||
// - sampleWorld() is a stateless fallback so chunk borders never seam
|
||||
// ======================================================================
|
||||
const CSIZE = CHUNK*CHUNK*WORLD_H;
|
||||
const chunks = new Map(); // "cx,cz" -> { data, group, dirty }
|
||||
const overrides = new Map(); // "x,y,z" -> id (persistent edits/structures/trees)
|
||||
const colCache = new Map(); // "x,z" -> column info
|
||||
const decorated = new Set(); // chunks whose trees were stamped
|
||||
|
||||
const ckey = (cx,cz)=> cx+","+cz;
|
||||
const bkey = (x,y,z)=> x+","+y+","+z;
|
||||
const cdiv = n => Math.floor(n/CHUNK);
|
||||
const localIdx = (lx,y,lz)=> (y*CHUNK + lz)*CHUNK + lx;
|
||||
|
||||
// write an override (force=false → first writer wins, used by trees so the mythos isn't overwritten)
|
||||
function ov(x,y,z,id,force){ if(y<0||y>=WORLD_H) return; const k=bkey(x,y,z);
|
||||
if(!force && overrides.has(k)) return; overrides.set(k,id); }
|
||||
|
||||
function columnInfo(x,z){ const k=x+","+z; let c=colCache.get(k); if(c) return c;
|
||||
const base = fbm(x*0.0065, z*0.0065, 5, 2.0, 0.5); // rolling continents
|
||||
const ridge = Math.pow(1 - Math.abs(fbm(x*0.012, z*0.012, 3, 2.0, 0.5)), 2); // mountain ridges
|
||||
let h = SEA + Math.round(base*22) + Math.round(ridge*16) - 3;
|
||||
h = Math.max(3, Math.min(WORLD_H-12, h));
|
||||
const temp = perlin2(x*0.0032+50.5, z*0.0032-50.5);
|
||||
const moist = perlin2(x*0.0032-80.5, z*0.0032+30.5);
|
||||
const sandy = (h <= SEA+1) || (temp>0.34 && moist<-0.05);
|
||||
const snowy = (h > SEA+25) || (temp < -0.42 && h>SEA+6);
|
||||
let topId = sandy ? B.SAND : snowy ? B.SNOW : (h<SEA? B.DIRT : B.GRASS);
|
||||
c = { h, topId, sandy, snowy }; colCache.set(k,c); return c;
|
||||
}
|
||||
|
||||
function terrainBlock(x,y,z){
|
||||
if(y<=0) return B.BEDROCK;
|
||||
if(y>=WORLD_H) return B.AIR;
|
||||
const info = columnInfo(x,z);
|
||||
if(y > info.h){ return (y<=SEA) ? B.WATER : B.AIR; }
|
||||
if(y === info.h) return info.topId;
|
||||
if(y > info.h-4) return info.sandy ? B.SAND : B.DIRT;
|
||||
// deep stone with rare veins for flavour
|
||||
const r = hash2(x*1.7+y*3.1, z*1.3-y*2.7);
|
||||
if(r < 0.010 && y < SEA-2) return B.LAPIS;
|
||||
if(r > 0.992) return B.COBBLE;
|
||||
return B.STONE;
|
||||
}
|
||||
|
||||
// single authoritative lookup (used at borders & by physics/raycast for any coord)
|
||||
function sampleWorld(x,y,z){ if(y<0) return B.BEDROCK; if(y>=WORLD_H) return B.AIR;
|
||||
const o = overrides.get(bkey(x,y,z)); if(o!==undefined) return o; return terrainBlock(x,y,z); }
|
||||
|
||||
function getBlock(x,y,z){ if(y<0) return B.BEDROCK; if(y>=WORLD_H) return B.AIR;
|
||||
const ch = chunks.get(ckey(cdiv(x),cdiv(z)));
|
||||
if(ch){ const lx=x-cdiv(x)*CHUNK, lz=z-cdiv(z)*CHUNK; return ch.data[localIdx(lx,y,lz)]; }
|
||||
return sampleWorld(x,y,z);
|
||||
}
|
||||
|
||||
// ---------- tree decoration (writes only to overrides; first-writer-wins) ----------
|
||||
function placeTree(x,by,z){ const r=mulberry32((x*73856093)^(z*19349663));
|
||||
const th = 4 + ((r()*3)|0);
|
||||
for(let i=0;i<th;i++) ov(x,by+i,z,B.LOG,false);
|
||||
const top = by+th;
|
||||
for(let dy=-2;dy<=1;dy++){ const ly=top+dy; const rad = dy<=0?2:1;
|
||||
for(let dx=-rad;dx<=rad;dx++)for(let dz=-rad;dz<=rad;dz++){
|
||||
if(Math.abs(dx)===rad && Math.abs(dz)===rad && r()<0.6) continue; // round the corners
|
||||
if(dx===0&&dz===0&&dy<0) continue; // keep trunk clear
|
||||
ov(x+dx, ly, z+dz, B.LEAVES, false);
|
||||
} }
|
||||
ov(x,top+1,z,B.LEAVES,false);
|
||||
}
|
||||
function decorate(cx,cz){ const k=ckey(cx,cz); if(decorated.has(k)) return; decorated.add(k);
|
||||
for(let lx=0;lx<CHUNK;lx++)for(let lz=0;lz<CHUNK;lz++){
|
||||
const x=cx*CHUNK+lx, z=cz*CHUNK+lz;
|
||||
if(x*x+z*z < 52*52) continue; // keep the mythos plaza clear of forest
|
||||
const info = columnInfo(x,z);
|
||||
if(info.topId!==B.GRASS || info.h<=SEA) continue;
|
||||
if(hash2(x*1.13+0.5, z*2.27+0.5) < 0.016) placeTree(x, info.h+1, z);
|
||||
}
|
||||
}
|
||||
|
||||
/* @@APPEND@@ */
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user