contrib: MiMo V2.5 (High) all cases

Adds MiMo V2.5 (High) MiMo Code artifacts for mythos-craft, pelican-cycling, and skeleton-watch. Maintainer-adjusted skeleton-watch visible calibre label to avoid blind Arena identity leakage.
This commit is contained in:
BaiLin-William authored and GitHub committed 2026-07-02 17:15:27 +01:00
1 parent acb27270f0
commit 1a6b884f75
4 files changed
+1762

No files matched your search

@@ -0,0 +1,628 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mythos Craft - 3D Voxel World</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{overflow:hidden;background:#000;font-family:'Courier New',monospace;color:#fff}
canvas{display:block}
#blocker{position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:100;cursor:pointer}
#blocker .inner{text-align:center;max-width:600px;padding:40px}
#blocker h1{font-size:48px;color:#4fc3f7;text-shadow:0 0 20px #4fc3f7;margin-bottom:10px}
#blocker h2{font-size:18px;color:#aaa;margin-bottom:30px}
#blocker .controls{text-align:left;background:rgba(255,255,255,0.05);border-radius:12px;padding:24px;margin-bottom:30px;line-height:2}
#blocker .controls span{color:#4fc3f7;font-weight:bold}
#blocker .start-btn{display:inline-block;padding:14px 48px;background:linear-gradient(135deg,#4fc3f7,#0288d1);color:#000;font-size:20px;font-weight:bold;border:none;border-radius:8px;cursor:pointer;transition:transform 0.2s}
#blocker .start-btn:hover{transform:scale(1.05)}
#crosshair{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:50;pointer-events:none}
#crosshair::before,#crosshair::after{content:'';position:absolute;background:rgba(255,255,255,0.8)}
#crosshair::before{width:20px;height:2px;top:-1px;left:-10px}
#crosshair::after{width:2px;height:20px;top:-10px;left:-1px}
#hotbar{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);display:flex;gap:4px;z-index:50;pointer-events:none}
.slot{width:52px;height:52px;border:2px solid rgba(255,255,255,0.3);border-radius:6px;display:flex;align-items:center;justify-content:center;position:relative;background:rgba(0,0,0,0.5)}
.slot.active{border-color:#4fc3f7;box-shadow:0 0 10px #4fc3f7}
.slot canvas{width:36px;height:36px;border-radius:3px}
.slot .num{position:absolute;top:2px;left:4px;font-size:10px;color:#aaa}
.slot .bname{position:absolute;bottom:-18px;font-size:9px;color:#aaa;white-space:nowrap;left:50%;transform:translateX(-50%)}
#hud{position:fixed;top:16px;left:16px;z-index:50;font-size:13px;color:rgba(255,255,255,0.7);pointer-events:none;line-height:1.8}
#info{position:fixed;bottom:16px;left:16px;z-index:50;font-size:12px;color:rgba(255,255,255,0.5);pointer-events:none;line-height:1.6}
</style>
</head>
<body>
<div id="blocker">
<div class="inner">
<h1>Mythos Craft</h1>
<h2>神话境界 - A Mythic Fantasy Voxel World</h2>
<div class="controls">
<span>WASD</span> - 移动 Move<br>
<span>鼠标</span> - 环视 Look Around<br>
<span>空格</span> - 跳跃 Jump<br>
<span>左键</span> - 破坏方块 Break Block<br>
<span>右键</span> - 放置方块 Place Block<br>
<span>1-9 / 滚轮</span> - 选择方块 Select Block<br>
<span>F</span> - 飞行模式 Toggle Fly
</div>
<button class="start-btn" id="startBtn">点击进入 Click to Start</button>
</div>
</div>
<div id="crosshair"></div>
<div id="hotbar"></div>
<div id="hud"></div>
<div id="info"></div>
<script>/**
* @license
* Copyright 2010-2021 Three.js Authors
* SPDX-License-Identifier: MIT
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="128",n=100,i=300,r=301,s=302,a=303,o=304,l=306,c=307,h=1e3,u=1001,d=1002,p=1003,m=1004,f=1005,g=1006,v=1007,y=1008,x=1009,_=1012,w=1014,b=1015,M=1016,S=1020,T=1022,E=1023,A=1026,L=1027,R=33776,C=33777,P=33778,D=33779,I=35840,N=35841,B=35842,z=35843,F=37492,O=37496,H=2300,G=2301,U=2302,k=2400,V=2401,W=2402,j=2500,q=2501,X=3e3,Y=3001,Z=3007,J=3002,Q=3004,K=3005,$=3006,tt=7680,et=35044,nt=35048,it="300 es";class rt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e<i;e++)n[e].call(this,t);t.target=null}}}const st=[];for(let t=0;t<256;t++)st[t]=(t<16?"0":"")+t.toString(16);let at=1234567;const ot=Math.PI/180,lt=180/Math.PI;function ct(){const t=4294967295*Math.random()|0,e=4294967295*Math.random()|0,n=4294967295*Math.random()|0,i=4294967295*Math.random()|0;return(st[255&t]+st[t>>8&255]+st[t>>16&255]+st[t>>24&255]+"-"+st[255&e]+st[e>>8&255]+"-"+st[e>>16&15|64]+st[e>>24&255]+"-"+st[63&n|128]+st[n>>8&255]+"-"+st[n>>16&255]+st[n>>24&255]+st[255&i]+st[i>>8&255]+st[i>>16&255]+st[i>>24&255]).toUpperCase()}function ht(t,e,n){return Math.max(e,Math.min(n,t))}function ut(t,e){return(t%e+e)%e}function dt(t,e,n){return(1-n)*t+n*e}function pt(t){return 0==(t&t-1)&&0!==t}function mt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function ft(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}var gt=Object.freeze({__proto__:null,DEG2RAD:ot,RAD2DEG:lt,generateUUID:ct,clamp:ht,euclideanModulo:ut,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:dt,damp:function(t,e,n,i){return dt(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(ut(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){return void 0!==t&&(at=t%2147483647),at=16807*at%2147483647,(at-1)/2147483646},degToRad:function(t){return t*ot},radToDeg:function(t){return t*lt},isPowerOfTwo:pt,ceilPowerOfTwo:mt,floorPowerOfTwo:ft,setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}}});class vt{constructor(t=0,e=0){this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t,e){return void 0!==e?(console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(t,e)):(this.x+=t.x,this.y+=t.y,this)}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t,e){return void 0!==e?(console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(t,e)):(this.x-=t.x,this.y-=t.y,this)}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=tLine truncated
</script>
<script>
'use strict';
// ============================================================
// Perlin Noise (classic 2D)
// ============================================================
const perm=[151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
const p=new Array(512);const g=new Array(512);
for(let i=0;i<512;i++){p[i]=perm[i&255];g[i]=perm[i&255]%12}
const grad3=[[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
function dot(gi,x,y){const v=grad3[gi];return v[0]*x+v[1]*y}
function noise(xin,yin){const F2=0.5*(Math.sqrt(3)-1);const G2=(3-Math.sqrt(3))/6;const s=(xin+yin)*F2;const i=Math.floor(xin+s);const j=Math.floor(yin+s);const t=(i+j)*G2;const X0=i-t;const Y0=j-t;const x0=xin-X0;const y0=yin-Y0;let i1,j1;if(x0>y0){i1=1;j1=0}else{i1=0;j1=1}const x1=x0-i1+G2;const y1=y0-j1+G2;const x2=x0-1+2*G2;const y2=y0-1+2*G2;const ii=i&255;const jj=j&255;const gi0=g[ii+p[jj]&511];const gi1=g[ii+i1+p[jj+j1]&511];const gi2=g[ii+1+p[jj+1]&511];let n0,n1,n2;let t0=0.5-x0*x0-y0*y0;if(t0<0)n0=0;else{t0*=t0;n0=t0*t0*dot(gi0,x0,y0)}let t1=0.5-x1*x1-y1*y1;if(t1<0)n1=0;else{t1*=t1;n1=t1*t1*dot(gi1,x1,y1)}let t2=0.5-x2*x2-y2*y2;if(t2<0)n2=0;else{t2*=t2;n2=t2*t2*dot(gi2,x2,y2)}return 70*(n0+n1+n2)}
function fbm(x,y,octaves=6,lacunarity=2,gain=0.5){let v=0,a=1,f=1,t=0;for(let i=0;i<octaves;i++){v+=a*noise(x*f,y*f);t+=a;a*=gain;f*=lacunarity}return v/t}
function getTerrainHeight(wx,wz){const s=0.008;let h=fbm(wx*s+5.3,wz*s+9.1,6,2,0.5)*30+25;const ridge=Math.abs(fbm(wx*0.003+100,wz*0.003+200,4,2,0.5))*25;h+=ridge;const mountain=fbm(wx*0.005+50,wz*0.005+50,3,2,0.5);if(mountain>0.2){h+=(mountain-0.2)*60}return Math.floor(h)}
// ============================================================
// Block Types
// ============================================================
const B={AIR:0,DIRT:1,GRASS:2,STONE:3,SAND:4,WATER:5,WOOD:6,LEAVES:7,BRICK:8,Obsidian:9,Crystal:10,Gold:11,Glowstone:12,Bone:13,Amethyst:14,DarkStone:15};
const BNAME={[B.DIRT]:'Dirt',[B.GRASS]:'Grass',[B.STONE]:'Stone',[B.SAND]:'Sand',[B.WATER]:'Water',[B.WOOD]:'Wood',[B.LEAVES]:'Leaves',[B.BRICK]:'Brick',[B.Obsidian]:'Obsidian',[B.Crystal]:'Crystal',[B.Gold]:'Gold',[B.Glowstone]:'Glow',[B.Bone]:'Bone',[B.Amethyst]:'Amethyst',[B.DarkStone]:'DkStone'};
const BCOLOR={
[B.DIRT]:{top:'#8B6914',side:'#6B4400',bottom:'#5a3800'},
[B.GRASS]:{top:'#228B22',side:'#6B4400',bottom:'#5a3800',sideTop:'#32CD32'},
[B.STONE]:{top:'#808080',side:'#707070',bottom:'#606060'},
[B.SAND]:{top:'#C2B280',side:'#B8A870',bottom:'#A89860'},
[B.WATER]:{top:'#1E90FF',side:'#1873CC',bottom:'#1060B0'},
[B.WOOD]:{top:'#8B4513',side:'#654321',bottom:'#8B4513'},
[B.LEAVES]:{top:'#0B6623',side:'#0A5520',bottom:'#094418'},
[B.BRICK]:{top:'#8B0000',side:'#A00000',bottom:'#700000'},
[B.Obsidian]:{top:'#1a0033',side:'#120022',bottom:'#0a0015'},
[B.Crystal]:{top:'#88CCFF',side:'#66AAEE',bottom:'#4488CC'},
[B.Gold]:{top:'#FFD700',side:'#DAA520',bottom:'#B8860B'},
[B.Glowstone]:{top:'#FFA500',side:'#FF8C00',bottom:'#CC7000'},
[B.Bone]:{top:'#F5F5DC',side:'#E8E0C8',bottom:'#D8CEB0'},
[B.Amethyst]:{top:'#9966CC',side:'#8855BB',bottom:'#7744AA'},
[B.DarkStone]:{top:'#333340',side:'#282830',bottom:'#1e1e25'}
};
const HOTBAR_BLOCKS=[B.GRASS,B.DIRT,B.STONE,B.WOOD,B.BRICK,B.Crystal,B.Gold,B.Glowstone,B.Bone];
// ============================================================
// Texture Generation (canvas-based per block type)
// ============================================================
function makeTex(hex,fn){const c=document.createElement('canvas');c.width=c.height=16;const x=c.getContext('2d');x.fillStyle=hex;x.fillRect(0,0,16,16);if(fn)fn(x);return c}
function noiseFill(basehex,ranges){const c=document.createElement('canvas');c.width=c.height=16;const x=c.getContext('2d');x.fillStyle=basehex;x.fillRect(0,0,16,16);const id=x.getImageData(0,0,16,16);for(let i=0;i<id.data.length;i+=4){const n=(Math.random()-0.5)*ranges;id.data[i]=Math.min(255,Math.max(0,id.data[i]+n));id.data[i+1]=Math.min(255,Math.max(0,id.data[i+1]+n));id.data[i+2]=Math.min(255,Math.max(0,id.data[i+2]+n))}x.putImageData(id,0,0);return c}
const textures={};
function genTextures(){
textures.dirt=noiseFill('#8B6914',25);
textures.stone=noiseFill('#808080',20);
textures.sand=noiseFill('#C2B280',15);
textures.wood_top=makeTex('#8B4513',c=>{c.strokeStyle='#654321';c.lineWidth=1;for(let i=2;i<16;i+=3){c.beginPath();c.arc(8,8,i,0,Math.PI*2);c.stroke()}});
textures.wood_side=makeTex('#654321',c=>{c.strokeStyle='#4a3010';c.lineWidth=1;for(let y=0;y<16;y+=4){c.beginPath();c.moveTo(0,y+2);c.bezierCurveTo(4,y,12,y+4,16,y+2);c.stroke()}});
textures.leaves=noiseFill('#0B6623',30);
textures.brick=makeTex('#8B0000',c=>{c.fillStyle='#d4a574';c.fillRect(0,0,16,16);c.fillStyle='#8B0000';for(let r=0;r<4;r++){const y=r*4;const off=r%2===1?4:0;for(let bx=-1;bx<4;bx++){c.fillRect(bx*8+off,y,7,3)}}});
textures.obsidian=noiseFill('#1a0033',12);
textures.crystal=makeTex('#88CCFF',c=>{c.globalAlpha=0.3;for(let i=0;i<8;i++){c.fillStyle=i%2?'#aaddff':'#6699cc';c.fillRect(Math.random()*12,Math.random()*12,3,3)}c.globalAlpha=1;c.strokeStyle='#ffffff44';c.lineWidth=0.5;c.strokeRect(2,2,12,12)});
textures.gold=makeTex('#FFD700',c=>{c.globalAlpha=0.2;for(let i=0;i<6;i++){c.fillStyle='#fff';c.fillRect(Math.random()*14,Math.random()*14,2,2)}c.globalAlpha=1});
textures.glowstone=makeTex('#FFA500',c=>{c.globalAlpha=0.4;for(let i=0;i<10;i++){c.fillStyle='#ffee88';const s=1+Math.random()*3;c.fillRect(Math.random()*14,Math.random()*14,s,s)}c.globalAlpha=1});
textures.bone=makeTex('#F5F5DC',c=>{c.strokeStyle='#d0c8a0';c.lineWidth=0.5;for(let i=0;i<5;i++){c.beginPath();c.moveTo(Math.random()*16,Math.random()*16);c.lineTo(Math.random()*16,Math.random()*16);c.stroke()}});
textures.amethyst=makeTex('#9966CC',c=>{c.globalAlpha=0.3;for(let i=0;i<8;i++){c.fillStyle=i%2?'#bb88ee':'#7744aa';c.fillRect(Math.random()*12,Math.random()*12,3,3)}c.globalAlpha=1});
textures.darkstone=noiseFill('#333340',15);
textures.grass_top=noiseFill('#228B22',25);
textures.grass_side=makeTex('#6B4400',c=>{
c.fillStyle='#6B4400';c.fillRect(0,0,16,16);
const id=c.getImageData(0,0,16,16);for(let i=0;i<id.data.length;i+=4){const n=(Math.random()-0.5)*20;id.data[i]=Math.min(255,Math.max(0,id.data[i]+n));id.data[i+1]=Math.min(255,Math.max(0,id.data[i+1]+n));id.data[i+2]=Math.min(255,Math.max(0,id.data[i+2]+n))}
c.putImageData(id,0,0);
c.fillStyle='#32CD32';for(let x=0;x<16;x++){const h=2+Math.floor(Math.random()*3);c.fillRect(x,0,1,h)}
});
textures.water=makeTex('#1E90FF',c=>{c.globalAlpha=0.6;c.fillStyle='#4db8ff';for(let i=0;i<4;i++){c.fillRect(0,i*4+1,16,1)}c.globalAlpha=1});
}
function getTexName(blockType,face){
if(blockType===B.GRASS){if(face===1)return'textures.grass_top';if(face===0||face===2||face===3||face===4||face===5)return'textures.grass_side'}
if(blockType===B.WOOD){if(face===1||face===0)return'textures.wood_top';return'textures.wood_side'}
const map={1:'dirt',3:'stone',4:'sand',5:'water',7:'leaves',8:'brick',9:'obsidian',10:'crystal',11:'gold',12:'glowstone',13:'bone',14:'amethyst',15:'darkstone'};
return'textures.'+(map[blockType]||'stone');
}
// ============================================================
// Chunk
// ============================================================
const CS=16,CH=64;
const DIRS=[{dx:1,dz:0},{dx:-1,dz:0},{dx:0,dz:1},{dx:0,dz:-1}];
const FACE_VERTS=[
[[1,0,0],[1,1,0],[1,1,1],[1,0,1]],
[[0,0,1],[0,1,1],[0,1,0],[0,0,0]],
[[0,1,1],[1,1,1],[1,1,0],[0,1,0]],
[[0,0,0],[1,0,0],[1,0,1],[0,0,1]],
[[0,0,1],[1,0,1],[1,1,1],[0,1,1]],
[[1,0,0],[0,0,0],[0,1,0],[1,1,0]]
];
const FACE_UVS=[[0,0],[0,1],[1,1],[1,0]];
const FACE_NORMALS=[[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]];
class Chunk{
constructor(cx,cz){
this.cx=cx;this.cz=cz;this.blocks=new Uint8Array(CS*CS*CH);this.mesh=null;this.dirty=true;this.generated=false;
}
idx(x,y,z){return x+z*CS+y*CS*CS}
get(x,y,z){if(x<0||x>=CS||y<0||y>=CH||z<0||z>=CS)return B.AIR;return this.blocks[this.idx(x,y,z)]}
set(x,y,z,t){if(x<0||x>=CS||y<0||y>=CH||z<0||z>=CS)return;this.blocks[this.idx(x,y,z)]=t;this.dirty=true}
generate(){
if(this.generated)return;this.generated=true;
for(let x=0;x<CS;x++){for(let z=0;z<CS;z++){
const wx=this.cx*CS+x,wz=this.cz*CS+z;
const h=getTerrainHeight(wx,wz);
for(let y=0;y<CH;y++){
let b=B.AIR;
if(y===0)b=B.Stone;
else if(y<h-4)b=B.Stone;
else if(y<h)b=B.DIRT;
else if(y===h){b=h>20?B.GRASS:B.SAND}
else if(y<=20&&y>h)b=B.WATER;
this.blocks[this.idx(x,y,z)]=b;
}
}}
}
getBlock(wx,wy,wz){
const lx=wx-this.cx*CS,lz=wz-this.cz*CS;
if(lx<0||lx>=CS||lz<0||lz>=CS||wy<0||wy>=CH)return B.AIR;
return this.blocks[this.idx(lx,wy,lz)];
}
setBlock(wx,wy,wz,t){
const lx=wx-this.cx*CS,lz=wz-this.cz*CS;
if(lx<0||lx>=CS||lz<0||lz>=CS||wy<0||wy>=CH)return;
this.blocks[this.idx(lx,wy,lz)]=t;this.dirty=true;
}
buildMesh(world){
if(!this.dirty)return;this.dirty=false;
if(this.mesh){this.mesh.geometry.dispose();this.mesh.material.dispose();this.mesh.parent?.remove(this.mesh)}
const pos=[],norms=[],uvs=[],cols=[],inds=[];let vi=0;
const atlW=4,atlH=4;
for(let x=0;x<CS;x++){for(let z=0;z<CS;z++){for(let y=0;y<CH;y++){
const bt=this.get(x,y,z);if(bt===B.AIR)continue;
for(let f=0;f<6;f++){
const nx=x+FACE_NORMALS[f][0],ny=y+FACE_NORMALS[f][1],nz=z+FACE_NORMALS[f][2];
let nb;
if(nx<0||nx>=CS||nz<0||nz>=CS){
const wbx=this.cx*CS+nx,wbz=this.cz*CS+nz;
nb=world.getBlock(wbx,ny,wbz);
}else if(ny<0||ny>=CH){nb=B.AIR}
else{nb=this.get(nx,ny,nz)}
if(nb!==B.AIR)continue;
const texName=getTexName(bt,f);
const ti=parseInt(texName.split('.')[1]);
const col=BCOLOR[bt];
const faceCol=f===1?col.top:f===0?col.bottom:col.side;
const r=parseInt(faceCol.slice(1,3),16)/255;
const g=parseInt(faceCol.slice(3,5),16)/255;
const b_=parseInt(faceCol.slice(5,7),16)/255;
const tx=ti%atlW,ty=Math.floor(ti/atlW);
const fu=tx/atlW,fv=ty/atlH,fuW=1/atlW,fvH=1/atlH;
for(let v=0;v<4;v++){
const vt=FACE_VERTS[f][v];
pos.push(x+vt[0],y+vt[1],z+vt[2]);
norms.push(FACE_NORMALS[f][0],FACE_NORMALS[f][1],FACE_NORMALS[f][2]);
uvs.push(fu+FACE_UVS[v][0]*fuW,fv+FACE_UVS[v][1]*fvH);
cols.push(r,g,b_);
}
inds.push(vi,vi+1,vi+2,vi,vi+2,vi+3);vi+=4;
}
}}}
if(pos.length===0)return;
const geo=new THREE.BufferGeometry();
geo.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));
geo.setAttribute('normal',new THREE.Float32BufferAttribute(norms,3));
geo.setAttribute('uv',new THREE.Float32BufferAttribute(uvs,2));
geo.setAttribute('color',new THREE.Float32BufferAttribute(cols,3));
geo.setIndex(inds);
const mat=new THREE.MeshStandardMaterial({map:atlas,vertexColors:true,side:THREE.FrontSide});
this.mesh=new THREE.Mesh(geo,mat);
this.mesh.position.set(this.cx*CS,0,this.cz*CS);
world.scene.add(this.mesh);
}
}
// ============================================================
// World
// ============================================================
class World{
constructor(scene){this.scene=scene;this.chunks=new Map();this.structures=new Set()}
key(cx,cz){return cx+','+cz}
getChunk(cx,cz){return this.chunks.get(this.key(cx,cz))}
ensureChunk(cx,cz){
let ch=this.getChunk(cx,cz);
if(!ch){ch=new Chunk(cx,cz);ch.generate();this.chunks.set(this.key(cx,cz),ch)}
return ch;
}
getBlock(wx,wy,wz){
const cx=Math.floor(wx/CS),cz=Math.floor(wz/CS);
const ch=this.getChunk(cx,cz);
return ch?ch.getBlock(wx,wy,wz):B.AIR;
}
setBlock(wx,wy,wz,t){
const cx=Math.floor(wx/CS),cz=Math.floor(wz/CS);
const ch=this.ensureChunk(cx,cz);
ch.setBlock(wx,wy,wz,t);
const lx=wx-cx*CS,lz=wz-cz*CS;
if(lx===0){const n=this.getChunk(cx-1,cz);if(n)n.dirty=true}
if(lx===CS-1){const n=this.getChunk(cx+1,cz);if(n)n.dirty=true}
if(lz===0){const n=this.getChunk(cx,cz-1);if(n)n.dirty=true}
if(lz===CS-1){const n=this.getChunk(cx,cz+1);if(n)n.dirty=true}
}
updateMeshes(playerX,playerZ){
const pcx=Math.floor(playerX/CS),pcz=Math.floor(playerZ/CS);
const R=5;
for(const[key,ch]of this.chunks){
const dx=ch.cx-pcx,dz=ch.cz-pcz;
if(Math.abs(dx)>R+2||Math.abs(dz)>R+2){if(ch.mesh){this.scene.remove(ch.mesh);ch.mesh.geometry.dispose();ch.mesh.material.dispose()}this.chunks.delete(key);continue}
ch.buildMesh(this);
}
for(let dx=-R;dx<=R;dx++){for(let dz=-R;dz<=R;dz++){
const cx=pcx+dx,cz=pcz+dz;
const ch=this.ensureChunk(cx,cz);ch.buildMesh(this);
}}
if(!this.structures.has('built')){this.buildStructures();this.structures.add('built')}
}
buildStructures(){
this.buildCrystalTower(40,40);this.buildStoneCircle(-40,30);
this.buildDragonRuins(30,-40);this.buildSkyIsland(-30,-30);
this.buildAncientLibrary(60,-20);
}
buildCrystalTower(sx,sz){
const h=getTerrainHeight(sx,sz);
for(let y=h+1;y<=h+25;y++){
const r=y<h+15?3:Math.max(1,3-Math.floor((y-h-15)/5));
for(let x=-r;x<=r;x++){for(let z=-r;z<=r;z++){
if(x===0&&z===0&&y<h+25)continue;
if(Math.abs(x)===r&&Math.abs(z)===r&&Math.random()>0.5)continue;
this.setBlock(sx+x,y,sz+z,B.Crystal);
}}
}
for(let i=0;i<6;i++){
const a=i/6*Math.PI*2,r=5;
const bx=sx+Math.round(Math.cos(a)*r),bz=sz+Math.round(Math.sin(a)*r);
const bh=getTerrainHeight(bx,bz);
for(let y=bh+1;y<=h+10;y++)this.setBlock(bx,y,bz,B.DarkStone);
}
for(let x=-1;x<=1;x++)for(let z=-1;z<=1;z++)this.setBlock(sx+x,h+26,sz+z,B.Glowstone);
this.setBlock(sx,h+27,sz,B.Amethyst);
}
buildStoneCircle(sx,sz){
const h=getTerrainHeight(sx,sz);
for(let x=-6;x<=6;x++)for(let z=-6;z<=6;z++){
if(x*x+z*z<=49&&x*x+z*z>=16){const y=h+1;if(x*x+z*z<=30)this.setBlock(sx+x,y,sz+z,B.Stone)}
}
for(let i=0;i<8;i++){
const a=i/8*Math.PI*2,r=5.5;
const bx=sx+Math.round(Math.cos(a)*r),bz=sz+Math.round(Math.sin(a)*r);
const bh=getTerrainHeight(bx,bz);
for(let y=bh+1;y<=bh+6+(i%2)*3;y++)this.setBlock(bx,y,bz,B.Stone);
this.setBlock(bx,bh+7+(i%2)*3,bz,B.Glowstone);
}
this.setBlock(sx,h+1,sz,B.Gold);this.setBlock(sx,h+2,sz,B.Glowstone);
}
buildDragonRuins(sx,sz){
const h=getTerrainHeight(sx,sz);
for(let i=0;i<12;i++){
const a=i/12*Math.PI*2;
const bx=sx+Math.round(Math.cos(a)*(3+i%3)),bz=sz+Math.round(Math.sin(a)*(3+i%3));
const bh=getTerrainHeight(bx,bz);
const rh=2+Math.floor(Math.random()*4);
for(let y=bh+1;y<=bh+rh;y++)this.setBlock(bx,y,bz,B.Bone);
}
for(let x=-2;x<=2;x++)for(let z=-4;z<=4;z++){
if(Math.abs(z)>2&&Math.random()>0.3)continue;
this.setBlock(sx+x,h+1,sz+z,B.DarkStone);
}
for(let i=0;i<3;i++){
const bx=sx-3+i*3,bz=sz;
const bh=getTerrainHeight(bx,bz);
this.setBlock(bx,bh+1,bz,B.Bone);this.setBlock(bx,bh+2,bz,B.Obsidian);
}
this.setBlock(sx,h+4,sz,B.Glowstone);
}
buildSkyIsland(sx,sz){
const baseY=50;
for(let x=-5;x<=5;x++)for(let z=-5;z<=5;z++){
const d=Math.sqrt(x*x+z*z);
if(d>5)continue;
const th=Math.floor(4*(1-d/5));
for(let y=-th;y<=0;y++){
const bt=y===0?B.GRASS:y>-2?B.DIRT:B.Stone;
this.setBlock(sx+x,baseY+y,sz+z,bt);
}
}
for(let i=0;i<3;i++){
const tx=sx+Math.floor(Math.random()*6)-3,tz=sz+Math.floor(Math.random()*6)-3;
const td=Math.sqrt((tx-sx)**2+(tz-sz)**2);
if(td>4)continue;
for(let y=1;y<=4+Math.floor(Math.random()*3);y++)this.setBlock(tx,baseY+y,tz,B.Wood);
for(let dx=-2;dx<=2;dx++)for(let dz=-2;dz<=2;dz++){
if(Math.abs(dx)===2&&Math.abs(dz)===2&&Math.random()>0.5)continue;
this.setBlock(tx+dx,baseY+5+Math.floor(Math.random()*2),tz+dz,B.LEAVES);
}
}
this.setBlock(sx,baseY+1,sz,B.Glowstone);
this.setBlock(sx,baseY+2,sz,B.Amethyst);
}
buildAncientLibrary(sx,sz){
const h=getTerrainHeight(sx,sz);
for(let x=-4;x<=4;x++)for(let z=-3;z<=3;z++){
this.setBlock(sx+x,h+1,sz+z,B.DarkStone);
if(Math.abs(x)===4||Math.abs(z)===3){
for(let y=h+2;y<=h+7;y++)this.setBlock(sx+x,y,sz+z,B.Brick);
}
}
for(let x=-3;x<=3;x+=2){
for(let y=h+2;y<=h+5;y++){this.setBlock(sx+x,y,sz-2,B.Wood);this.setBlock(sx+x,y,sz+2,B.Wood)}
}
for(let z=-2;z<=2;z++){
this.setBlock(sx-3,h+6,sz+z,B.Brick);this.setBlock(sx+3,h+6,sz+z,B.Brick);
}
for(let x=-2;x<=2;x++)for(let z=-1;z<=1;z++)this.setBlock(sx+x,h+7,sz+z,B.Brick);
this.setBlock(sx,h+8,sz,B.Glowstone);
for(let i=0;i<4;i++){
const a=i/4*Math.PI*2;
this.setBlock(sx+Math.round(Math.cos(a)*5),h+1,sz+Math.round(Math.sin(a)*5),B.Glowstone);
}
}
}
// ============================================================
// Atlas texture
// ============================================================
let atlas;
function genAtlas(){
const s=16,n=4,c=document.createElement('canvas');c.width=c.height=s*n;
const ctx=c.getContext('2d');
const order=['dirt','grass_side','grass_top','stone','sand','water','wood_side','wood_top','leaves','brick','obsidian','crystal','gold','glowstone','bone','amethyst','darkstone','dirt','dirt','dirt','dirt','dirt','dirt','dirt'];
for(let i=0;i<n*n;i++){
const tn=order[i]||'dirt';
const tc=textures[tn]||textures.dirt;
const col=i%n,row=Math.floor(i/n);
ctx.drawImage(tc,col*s,row*s,s,s);
}
const tex=new THREE.CanvasTexture(c);
tex.magFilter=THREE.NearestFilter;tex.minFilter=THREE.NearestFilter;tex.wrapS=tex.wrapT=THREE.RepeatWrapping;
atlas=tex;
}
// ============================================================
// Player
// ============================================================
class Player{
constructor(camera){
this.camera=camera;
this.pos=new THREE.Vector3(8,40,8);
this.vel=new THREE.Vector3(0,0,0);
this.yaw=0;this.pitch=0;
this.onGround=false;this.flying=false;
this.keys={};
this.height=1.7;this.speed=5;this.flySpeed=10;this.jumpVel=8;
this.selectedSlot=0;
document.addEventListener('keydown',e=>{
this.keys[e.code]=true;
if(e.code==='KeyF'){this.flying=!this.flying;this.vel.y=0}
if(e.code>='Digit1'&&e.code<='Digit9'){this.selectedSlot=parseInt(e.code[5])-1;updateHotbar()}
if(e.code==='Space'&&this.flying)this.vel.y=this.flySpeed;
});
document.addEventListener('keyup',e=>{this.keys[e.code]=false;if(e.code==='Space'&&this.flying)this.vel.y=0});
document.addEventListener('wheel',e=>{this.selectedSlot=(this.selectedSlot+(e.deltaY>0?1:-1)+9)%9;updateHotbar()});
document.addEventListener('mousemove',e=>{
if(document.pointerLockElement!==document.body)return;
this.yaw-=e.movementX*0.002;this.pitch-=e.movementY*0.002;
this.pitch=Math.max(-Math.PI/2+0.01,Math.min(Math.PI/2-0.01,this.pitch));
});
document.addEventListener('mousedown',e=>{
if(document.pointerLockElement!==document.body)return;
if(e.button===0)breakBlock();else if(e.button===2)placeBlock();
});
document.addEventListener('contextmenu',e=>e.preventDefault());
}
update(dt,world){
const sp=this.flying?this.flySpeed:this.speed;
const fwd=new THREE.Vector3(-Math.sin(this.yaw),0,-Math.cos(this.yaw));
const right=new THREE.Vector3(Math.cos(this.yaw),0,-Math.sin(this.yaw));
const move=new THREE.Vector3(0,0,0);
if(this.keys['KeyW'])move.add(fwd);if(this.keys['KeyS'])move.sub(fwd);
if(this.keys['KeyA'])move.sub(right);if(this.keys['KeyD'])move.add(right);
if(move.length()>0)move.normalize().multiplyScalar(sp);
this.vel.x=move.x;this.vel.z=move.z;
if(!this.flying){
this.vel.y-=20*dt;
if(this.keys['Space']&&this.onGround){this.vel.y=this.jumpVel}
}
const isSolid=(bx,by,bz)=>{const t=world.getBlock(bx,by,bz);return t!==B.AIR&&t!==B.WATER};
const halfW=0.3,h=this.height;
// Move X
this.pos.x+=this.vel.x*dt;
if(isSolid(Math.floor(this.pos.x+halfW),Math.floor(this.pos.y-1),Math.floor(this.pos.z))||
isSolid(Math.floor(this.pos.x+halfW),Math.floor(this.pos.y-0.1),Math.floor(this.pos.z))||
isSolid(Math.floor(this.pos.x-halfW),Math.floor(this.pos.y-1),Math.floor(this.pos.z))||
isSolid(Math.floor(this.pos.x-halfW),Math.floor(this.pos.y-0.1),Math.floor(this.pos.z))){
this.pos.x-=this.vel.x*dt;this.vel.x=0}
// Move Z
this.pos.z+=this.vel.z*dt;
if(isSolid(Math.floor(this.pos.x),Math.floor(this.pos.y-1),Math.floor(this.pos.z+halfW))||
isSolid(Math.floor(this.pos.x),Math.floor(this.pos.y-0.1),Math.floor(this.pos.z+halfW))||
isSolid(Math.floor(this.pos.x),Math.floor(this.pos.y-1),Math.floor(this.pos.z-halfW))||
isSolid(Math.floor(this.pos.x),Math.floor(this.pos.y-0.1),Math.floor(this.pos.z-halfW))){
this.pos.z-=this.vel.z*dt;this.vel.z=0}
// Move Y
this.pos.y+=this.vel.y*dt;
const feetY=this.pos.y-h;
if(this.vel.y<=0&&isSolid(Math.floor(this.pos.x),Math.floor(feetY),Math.floor(this.pos.z))){
this.pos.y=Math.floor(feetY)+1+h;this.vel.y=0;this.onGround=true;
}else if(this.vel.y>0&&isSolid(Math.floor(this.pos.x),Math.floor(this.pos.y),Math.floor(this.pos.z))){
this.pos.y=Math.floor(this.pos.y);this.vel.y=0;
}else{
this.onGround=false;
}
this.camera.position.copy(this.pos);
const lookDir=new THREE.Vector3(-Math.sin(this.yaw)*Math.cos(this.pitch),Math.sin(this.pitch),-Math.cos(this.yaw)*Math.cos(this.pitch));
this.camera.lookAt(this.pos.clone().add(lookDir));
}
}
// ============================================================
// Raycasting
// ============================================================
const raycaster=new THREE.Raycaster();
raycaster.far=8;
function raycast(world,camera){
raycaster.setFromCamera({x:0,y:0},camera);
const dir=raycaster.ray.direction;
const origin=raycaster.ray.origin.clone();
const step=0.1;
let prev=null;
for(let t=0;t<8;t+=step){
const p=origin.clone().add(dir.clone().multiplyScalar(t));
const bx=Math.floor(p.x),by=Math.floor(p.y),bz=Math.floor(p.z);
const bt=world.getBlock(bx,by,bz);
if(bt!==B.AIR&&bt!==B.WATER){
return{pos:{x:bx,y:by,z:bz},place:prev,bt};
}
prev={x:bx,y:by,z:bz};
}
return null;
}
function breakBlock(){
if(!currentWorld||!player)return;
const hit=raycast(currentWorld,player.camera);
if(hit){currentWorld.setBlock(hit.pos.x,hit.pos.y,hit.pos.z,B.AIR)}
}
function placeBlock(){
if(!currentWorld||!player)return;
const hit=raycast(currentWorld,player.camera);
if(!hit||!hit.place)return;
const p=hit.place;
const pp=player.pos;
if(Math.abs(p.x+0.5-pp.x)<1&&Math.abs(p.z+0.5-pp.z)<1&&p.y+1>=pp.y-1.7&&p.y<=pp.y+0.1)return;
currentWorld.setBlock(p.x,p.y,p.z,HOTBAR_BLOCKS[player.selectedSlot]);
}
// ============================================================
// Hotbar
// ============================================================
function buildHotbar(){
const hb=document.getElementById('hotbar');hb.innerHTML='';
HOTBAR_BLOCKS.forEach((bt,i)=>{
const slot=document.createElement('div');slot.className='slot'+(i===player?.selectedSlot?' active':'');
const num=document.createElement('div');num.className='num';num.textContent=i+1;slot.appendChild(num);
const c=document.createElement('canvas');c.width=c.height=16;
const ctx=c.getContext('2d');
const tn=getTexName(bt,1).split('.')[1];
ctx.drawImage(textures[tn]||textures.stone,0,0,16,16);
slot.appendChild(c);
const nm=document.createElement('div');nm.className='bname';nm.textContent=BNAME[bt]||'?';slot.appendChild(nm);
hb.appendChild(slot);
});
}
function updateHotbar(){buildHotbar()}
// ============================================================
// Highlight
// ============================================================
let highlightMesh;
function createHighlight(){
const geo=new THREE.BoxGeometry(1.01,1.01,1.01);
const edges=new THREE.EdgesGeometry(geo);
highlightMesh=new THREE.LineSegments(edges,new THREE.LineBasicMaterial({color:0xffffff,transparent:true,opacity:0.5}));
highlightMesh.visible=false;
}
// ============================================================
// Main
// ============================================================
let currentWorld,player;
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
renderer.setClearColor(0x87CEEB);
document.body.prepend(renderer.domElement);
const scene=new THREE.Scene();
scene.fog=new THREE.Fog(0x87CEEB,60,120);
const camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,200);
const ambientLight=new THREE.AmbientLight(0x6688aa,0.6);scene.add(ambientLight);
const sunLight=new THREE.DirectionalLight(0xfff5e0,1.0);
sunLight.position.set(50,80,30);sunLight.castShadow=true;
sunLight.shadow.mapSize.set(2048,2048);sunLight.shadow.camera.near=0.5;sunLight.shadow.camera.far=200;
sunLight.shadow.camera.left=-60;sunLight.shadow.camera.right=60;sunLight.shadow.camera.top=60;sunLight.shadow.camera.bottom=-60;
scene.add(sunLight);
genTextures();genAtlas();
currentWorld=new World(scene);
player=new Player(camera);
createHighlight();scene.add(highlightMesh);
// Sky dome
const skyGeo=new THREE.SphereGeometry(150,16,16);
const skyMat=new THREE.MeshBasicMaterial({color:0x87CEEB,side:THREE.BackSide});
scene.add(new THREE.Mesh(skyGeo,skyMat));
// Sun visual
const sunGeo=new THREE.SphereGeometry(5,16,16);
const sunMat=new THREE.MeshBasicMaterial({color:0xFFDD44});
const sunMesh=new THREE.Mesh(sunGeo,sunMat);
sunMesh.position.copy(sunLight.position);scene.add(sunMesh);
// Init position
const initH=getTerrainHeight(8,8);
player.pos.set(8,initH+3,8);
currentWorld.updateMeshes(player.pos.x,player.pos.z);
buildHotbar();
let lastTime=performance.now(),frameCount=0,fps=0,fpsTime=0;
const clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate);
const dt=Math.min(clock.getDelta(),0.05);
const now=performance.now();
frameCount++;fpsTime+=dt;
if(fpsTime>=1){fps=Math.round(frameCount/fpsTime);frameCount=0;fpsTime=0}
player.update(dt,currentWorld);
currentWorld.updateMeshes(player.pos.x,player.pos.z);
const hit=raycast(currentWorld,camera);
if(hit){highlightMesh.visible=true;highlightMesh.position.set(hit.pos.x+0.5,hit.pos.y+0.5,hit.pos.z+0.5)}
else{highlightMesh.visible=false}
sunMesh.position.set(player.pos.x+50,80,player.pos.z+30);
document.getElementById('hud').innerHTML=`FPS: ${fps}<br>XYZ: ${player.pos.x.toFixed(1)}, ${player.pos.y.toFixed(1)}, ${player.pos.z.toFixed(1)}<br>Mode: ${player.flying?'Flying':'Walking'}`;
renderer.render(scene,camera);
}
// Pointer lock
const blocker=document.getElementById('blocker');
document.getElementById('startBtn').addEventListener('click',()=>{document.body.requestPointerLock()});
document.addEventListener('pointerlockchange',()=>{
if(document.pointerLockElement===document.body){blocker.style.display='none'}
else{blocker.style.display='flex'}
});
window.addEventListener('resize',()=>{
camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
});
animate();
</script>
</body>
</html>