WWW.YOUINFO.SITE
标签聚合 TL

/tag/TL

LinuxDo 最新话题 · 2026-06-12 07:14:13+08:00 · tech

前言 前面我们 基于MDPC-my-dream-proxy-client 很容易实现了一个 hy2 翻墙客户端(壳) 再来试试 sing-box 和 anytls 协议 面向GPT开发 开发人员: Hermes 对接 mimo-v2.5 学习知识 把 G站/SagerNet/sing-box 按最新的 release tag clone一份本地代码. 不要放tmp, 未来要进一步分析. 是长期任务. 分析 sing-box 的使用方法, 配置文件用什么格式 用怎样的命令形式启动 sing-box 内核 有没有测试 配置文件 是否合法的 sing-box 命令? 这是 sing-box 的文档 https://sing-box.sagernet.org/ 对照 你的分析, 进行对比验证 把你的分析结论保存为 .md 文件 我要使用 sing-box 的 anytls 协议 你分析源码找到应该如何使用, 包括服务端 和 客户端 另存为一份 .md 文件 开发 - 基本功能 基于 /home/ubuntu/my-dream-proxy-client/ 这个项目进行开发 你先学习一下这个项目 现在要继续 开发支持 sing-box 内核 支持 anytls 协议 参考以下分析报告 ~/repos/sing-box/ANALYSIS.md — 项目分析(配置格式、启动命令等) ~/repos/sing-box/ANYTLS.md — AnyTLS 使用指南 先不要实施编码, 先和我讨论设计思路 sing-box 也是独立实例 API 端口 18280 sing-box 配置文件 多文件同目录 启动命令 sing-box run -C confdir/ 确定每个配置文件中只有一个json块, 比如, inbound.json 里面只有 inbound , outbound.json 里面只有 outbound 类似 xray 的处理方式, 对每个 json 配置文件, 都有一个HTML页面. 先只实现JSON文本编辑框, 表单以后再设计. 测试和调试 略 功能类的测试, 先让Agent自己搭环境测试, 遍历各种组合环境. 最后再由人上手. UI设计, 操作逻辑, 这些由人设计, 由人测试, 体验, 提改进要求. 开发 - outbound表单 之前 xray 的 outbound 只有 1个 proxy 和 1 个 direct 这次我加点功能, 最终的 outbound 页面是这样的 开发 - route 预设模板 route页面增加2个预设模板 一个是 “geosite-geolocation-!cn” 走 proxy, 默认 direct 一个是 “geosite-cn” “geoip-cn” “ip_is_private” 走 direct, 默认走 proxy 开发 - dns 预设模板 dns 页面增加2个预设模板 一个是 “rule_set”: “geosite-geolocation-!cn” 让 “google” 解析, 默认 “system” 解析 一个是 “rule_set”: “geosite-cn” 让 "system"解析, 默认 “google” 解析 Github G站/crazypeace/my-dream-proxy-client ======== 后记 连操作手册都是让Agent写的. 先让Agent以新用户的身份, 做一个完整的测试, 从 release 页面下载 zip 包开始. 然后让TA把刚才的测试过程总结成一份操作手册. G站/crazypeace/my-dream-proxy-client/blob/main/README.md#my-dream-proxy-client-使用手册-配合sing-box内核 1 个帖子 - 1 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-11 01:34:02+08:00 · tech

Blazing fast inference: By shifting the decode bottleneck from memory-bandwidth to compute, DiffusionGemma generates up to 4x faster token output on dedicated GPUs. (1000+ tokens per second on a single NVIDIA H100, 700+ tokens per second on NVIDIA GeForce RTX 5090). 一些补充 Diffusion是一种不同于主流文本大模型Next Token Predict的模型架构,常用于图片生成领域中。NTP是从左向右逐个token生成的,而Diffusion则是给定一块空白区域,模型预测这片区域的每个位置可能的内容,并一次次进行纠错,最终生成完整内容。 14 个帖子 - 9 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-10 14:02:49+08:00 · tech

MarcellusAshlyn8642@outlook.com----rp640919----9e5f94bc-e8a4-4e73-b8be-63364c29d753----M.C514_SN1.0.U.-CscP7nuccauR23P4fTSaIFZYejrXN!uf47fi61z6G1jB5kCoBRJNr!KM7HP4hrVitIDfvfdUipv340FXvmVw WTS8N7VP7WMV9qNTfb9gG9ZhPB1RRxkyEU67pJh49VTHH8m1lH6U8sHJ2Ozb gbBvML7bN8U8a4TmViJgT!coX!3sFRv54MeWkYyTJQBG6PoLcCV5ou!I05BPbZBSzjSnyIXk4pSAf4NaOVTrw5l r5nbmvhPhGi!W3Rf jJptnRQHZpyvQ9YUEi88vKnYdpVaPvo2wWBQXMdLNhFkblvKnDaI2diIMl3AUw4wGq7fngB6z8fPhwQiJ9rhANdC4LthTk X2cNyp0VNL2aK8OTLHto7GDKu8mhlEBm9JFOOWvnXPxsWQ6Oo!NUU0IsEDJ4w$ VegaDiana7991@outlook.com----di820060----9e5f94bc-e8a4-4e73-b8be-63364c29d753----M.C561_BAY.0.U.-Ct873V!DAwp6Pg396 Bz75dJEl86zpOocaySzZBtEtksm5sK5WESTPznGYxp62G!o5niyoLuYRDDvXIYBVuSxT2i2jkuEq43ZoA88QzLr0UfVwzBLq72roonoOfofBINm WiWk90 5zIi81Tjir0yWwLORRhBMTvUsCf*!t3gJC EGDhOttSHUI02!7!Vg7nUgcim!h1nLKYPqqMeygjc3c1iOp9mCmfrrnAdNyMLQdNwcf3TJhG5ZewydwoUVxiR!YsA!UZnSNNKkfcwBqR kA9pl4Jf3hg7C9uhkAf7P56NdoxxLb!Miasm!hfANdpHzRMKO3Zl2sKKcMFHyjMAtt6wuLopVXSH3IyA76pfIaLEqnnxVXMIVnV51kyE7bzxg$$ 3 个帖子 - 2 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-10 13:07:20+08:00 · tech

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>MineJS — Minecraft Clone</title> <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet"> <style> *{margin:0;padding:0;box-sizing:border-box;user-select:none} html,body{width:100%;height:100%;overflow:hidden;background:#000;font-family:'Press Start 2P',monospace} canvas{display:block} #gameCanvas{position:absolute;inset:0} .pixel{image-rendering:pixelated;image-rendering:crisp-edges} /* ---------- HUD ---------- */ #hud{position:absolute;inset:0;pointer-events:none;display:none} #crosshair{position:absolute;left:50%;top:50%;width:20px;height:20px;transform:translate(-50%,-50%);mix-blend-mode:difference} #crosshair:before,#crosshair:after{content:'';position:absolute;background:#fff} #crosshair:before{left:9px;top:0;width:2px;height:20px} #crosshair:after{top:9px;left:0;width:20px;height:2px} #hotbar{position:absolute;bottom:8px;left:50%;transform:translateX(-50%);display:flex;gap:0;background:rgba(0,0,0,.45);border:2px solid #1a1a1a;outline:2px solid rgba(255,255,255,.25)} .slot{width:46px;height:46px;border:2px solid #555;background:rgba(40,40,40,.6);display:flex;align-items:center;justify-content:center;position:relative} .slot.sel{border:2px solid #fff;background:rgba(90,90,90,.7);box-shadow:0 0 6px rgba(255,255,255,.6) inset} .slot canvas{width:36px;height:36px} #hearts{position:absolute;bottom:62px;left:50%;transform:translateX(-50%);font-size:15px;letter-spacing:2px;text-shadow:2px 2px 0 #000;font-family:Arial} #itemname{position:absolute;bottom:92px;left:50%;transform:translateX(-50%);color:#fff;font-size:10px;text-shadow:2px 2px #000;opacity:0;transition:opacity .5s} #debug{position:absolute;top:6px;left:6px;color:#fff;font-size:8px;line-height:1.8;text-shadow:1px 1px #000;background:rgba(0,0,0,.25);padding:6px} #vignette{position:absolute;inset:0;background:radial-gradient(ellipse at center,transparent 55%,rgba(0,0,0,.35) 100%)} #damageFlash{position:absolute;inset:0;background:rgba(255,0,0,.35);opacity:0;transition:opacity .4s} #waterOverlay{position:absolute;inset:0;background:rgba(20,60,160,.35);display:none} /* ---------- Screens ---------- */ .screen{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;text-align:center;z-index:10} #titleScreen{background:linear-gradient(#3a7bd5 0%,#79a7e8 45%,#3b7a2a 45.2%,#2a5c1e 100%)} #titleScreen:before{content:'';position:absolute;inset:0;background:repeating-conic-gradient(rgba(0,0,0,.06) 0 25%,transparent 0 50%) 0 0/32px 32px;opacity:.6} h1{font-size:42px;color:#fff;text-shadow:4px 4px 0 #3f3f3f, 8px 8px 0 rgba(0,0,0,.3);margin-bottom:8px;letter-spacing:4px;position:relative} .sub{color:#ffff55;font-size:11px;text-shadow:2px 2px #3f3f00;margin-bottom:34px;transform:rotate(-4deg);animation:pulse 1s infinite;position:relative} @keyframes pulse{50%{transform:rotate(-4deg) scale(1.08)}} .btn{font-family:inherit;font-size:12px;color:#fff;background:#6f6f6f;border:2px solid #000;box-shadow:inset 2px 2px 0 rgba(255,255,255,.45),inset -2px -2px 0 rgba(0,0,0,.45);padding:14px 40px;margin:6px;cursor:pointer;position:relative} .btn:hover{background:#7f8fbf} .controls{font-size:8px;line-height:2.2;color:#ddd;margin-top:28px;text-shadow:1px 1px #000;position:relative} #pauseScreen,#deathScreen{background:rgba(0,0,0,.55);display:none} #deathScreen{background:rgba(120,0,0,.5)} #invScreen{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:#c6c6c6;border:4px solid #555;box-shadow:inset 3px 3px 0 #fff,inset -3px -3px 0 #888,0 0 0 3px #000;padding:14px;display:none;z-index:20} #invScreen h3{font-size:10px;color:#404040;margin-bottom:10px} #invGrid{display:grid;grid-template-columns:repeat(9,44px);gap:2px} .invSlot{width:44px;height:44px;background:#8b8b8b;box-shadow:inset 2px 2px 0 #373737,inset -2px -2px 0 #fff;display:flex;align-items:center;justify-content:center;cursor:pointer} .invSlot:hover{background:#a8a8c0} .invSlot canvas{width:32px;height:32px} #loadingText{font-size:10px;margin-top:20px;color:#fff;text-shadow:2px 2px #000;position:relative} </style> </head> <body> <div id="hud"> <div id="vignette"></div> <div id="waterOverlay"></div> <div id="damageFlash"></div> <div id="crosshair"></div> <div id="debug"></div> <div id="hearts"></div> <div id="itemname"></div> <div id="hotbar"></div> </div> <div id="invScreen"><h3>Select Block (E / Esc to close)</h3><div id="invGrid"></div></div> <div id="titleScreen" class="screen"> <h1>MINEJS</h1> <div class="sub">Now in JavaScript!</div> <button class="btn" id="playBtn" disabled>Generating World...</button> <button class="btn" id="newWorldBtn">New World (delete save)</button> <div class="controls"> WASD move SPACE jump SHIFT sneak double-W sprint<br> LMB break RMB place MMB pick block WHEEL/1-9 hotbar<br> E inventory F fly N skip day/night G/H spawn pig/zombie </div> <div id="loadingText"></div> </div> <div id="pauseScreen" class="screen"><h1 style="font-size:24px">PAUSED</h1> <button class="btn" id="resumeBtn">Back to Game</button> <button class="btn" id="resetBtn">Delete World & Restart</button></div> <div id="deathScreen" class="screen"><h1 style="font-size:24px">You Died!</h1> <button class="btn" id="respawnBtn">Respawn</button></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script> /* ============================================================ MineJS — a Minecraft clone in one file ============================================================ */ 'use strict'; const $=id=>document.getElementById(id); const clamp=(v,a,b)=>v<a?a:v>b?b:v; const lerp=(a,b,t)=>a+(b-a)*t; const smooth=(a,b,x)=>{const t=clamp((x-a)/(b-a),0,1);return t*t*(3-2*t);}; /* ---------------- RNG + Perlin noise ---------------- */ 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;};} let SEED; function makePerlin(seed){ const p=new Uint8Array(512),perm=[];for(let i=0;i<256;i++)perm[i]=i; const rng=mulberry32(seed); for(let i=255;i>0;i--){const j=(rng()*(i+1))|0;[perm[i],perm[j]]=[perm[j],perm[i]];} for(let i=0;i<512;i++)p[i]=perm[i&255]; const fade=t=>t*t*t*(t*(t*6-15)+10); const grad=(h,x,y,z)=>{const u=h<8?x:y,v=h<4?y:(h===12||h===14?x:z);return((h&1)?-u:u)+((h&2)?-v:v);}; function n3(x,y,z){ const X=Math.floor(x)&255,Y=Math.floor(y)&255,Z=Math.floor(z)&255; x-=Math.floor(x);y-=Math.floor(y);z-=Math.floor(z); const u=fade(x),v=fade(y),w=fade(z); const A=p[X]+Y,AA=p[A]+Z,AB=p[A+1]+Z,B=p[X+1]+Y,BA=p[B]+Z,BB=p[B+1]+Z; return lerp(lerp(lerp(grad(p[AA]&15,x,y,z),grad(p[BA]&15,x-1,y,z),u), lerp(grad(p[AB]&15,x,y-1,z),grad(p[BB]&15,x-1,y-1,z),u),v), lerp(lerp(grad(p[AA+1]&15,x,y,z-1),grad(p[BA+1]&15,x-1,y,z-1),u), lerp(grad(p[AB+1]&15,x,y-1,z-1),grad(p[BB+1]&15,x-1,y-1,z-1),u),v),w); } return{n3,n2:(x,y)=>n3(x,y,0)}; } let perlin; function fbm2(x,y,oct){let v=0,a=1,f=1,m=0;for(let i=0;i<oct;i++){v+=perlin.n2(x*f,y*f)*a;m+=a;a*=.5;f*=2;}return v/m;} function h3(x,y,z){let n=(x|0)*73856093^(y|0)*19349663^(z|0)*83492791^SEED;n=Math.imul(n^(n>>>13),1274126177);n^=n>>>16;return(n>>>0)/4294967296;} /* ---------------- Block definitions ---------------- */ const B={AIR:0,GRASS:1,DIRT:2,STONE:3,COBBLE:4,PLANKS:5,LOG:6,LEAVES:7,SAND:8,SANDSTONE:9,GRAVEL:10,BRICK:11,GLASS:12,GLOW:13,SNOWGRASS:14,SNOW:15,WATER:16,COAL:17,IRON:18,GOLD:19,DIAMOND:20,BEDROCK:21,CACTUS:22,FLOWER_R:23,FLOWER_Y:24,TALLGRASS:25}; const T={GRASS_TOP:0,GRASS_SIDE:1,DIRT:2,STONE:3,SAND:4,WATER:5,LOG_SIDE:6,LOG_TOP:7,LEAVES:8,PLANKS:9,COBBLE:10,GLASS:11,COAL:12,IRON:13,GOLD:14,DIAMOND:15,BEDROCK:16,SNOW_TOP:17,SNOW_SIDE:18,GRAVEL:19,BRICK:20,FLOWER_R:21,FLOWER_Y:22,TALLGRASS:23,CACTUS_SIDE:24,CACTUS_TOP:25,SANDSTONE:26,GLOW:27,CRACK0:32}; const D=[];// block defs function def(id,name,top,side,bottom,o){D[id]=Object.assign({name,top,side,bottom,solid:true,transparent:false,cross:false,cullSame:false,hard:1,icon:side},o||{});} def(B.AIR,'Air',0,0,0,{solid:false,transparent:true,hard:0}); def(B.GRASS,'Grass Block',T.GRASS_TOP,T.GRASS_SIDE,T.DIRT,{hard:.6}); def(B.DIRT,'Dirt',T.DIRT,T.DIRT,T.DIRT,{hard:.5}); def(B.STONE,'Stone',T.STONE,T.STONE,T.STONE,{hard:1.5}); def(B.COBBLE,'Cobblestone',T.COBBLE,T.COBBLE,T.COBBLE,{hard:1.6}); def(B.PLANKS,'Oak Planks',T.PLANKS,T.PLANKS,T.PLANKS,{hard:1}); def(B.LOG,'Oak Log',T.LOG_TOP,T.LOG_SIDE,T.LOG_TOP,{hard:1}); def(B.LEAVES,'Oak Leaves',T.LEAVES,T.LEAVES,T.LEAVES,{hard:.25,transparent:true}); def(B.SAND,'Sand',T.SAND,T.SAND,T.SAND,{hard:.5}); def(B.SANDSTONE,'Sandstone',T.SANDSTONE,T.SANDSTONE,T.SANDSTONE,{hard:1.3}); def(B.GRAVEL,'Gravel',T.GRAVEL,T.GRAVEL,T.GRAVEL,{hard:.6}); def(B.BRICK,'Bricks',T.BRICK,T.BRICK,T.BRICK,{hard:1.6}); def(B.GLASS,'Glass',T.GLASS,T.GLASS,T.GLASS,{hard:.3,transparent:true,cullSame:true}); def(B.GLOW,'Glowstone',T.GLOW,T.GLOW,T.GLOW,{hard:.4}); def(B.SNOWGRASS,'Snowy Grass',T.SNOW_TOP,T.SNOW_SIDE,T.DIRT,{hard:.6}); def(B.SNOW,'Snow Block',T.SNOW_TOP,T.SNOW_TOP,T.SNOW_TOP,{hard:.4}); def(B.WATER,'Water',T.WATER,T.WATER,T.WATER,{solid:false,transparent:true,cullSame:true,liquid:true,hard:0}); def(B.COAL,'Coal Ore',T.COAL,T.COAL,T.COAL,{hard:2}); def(B.IRON,'Iron Ore',T.IRON,T.IRON,T.IRON,{hard:2.2}); def(B.GOLD,'Gold Ore',T.GOLD,T.GOLD,T.GOLD,{hard:2.2}); def(B.DIAMOND,'Diamond Ore',T.DIAMOND,T.DIAMOND,T.DIAMOND,{hard:2.5}); def(B.BEDROCK,'Bedrock',T.BEDROCK,T.BEDROCK,T.BEDROCK,{hard:-1}); def(B.CACTUS,'Cactus',T.CACTUS_TOP,T.CACTUS_SIDE,T.CACTUS_TOP,{hard:.4}); def(B.FLOWER_R,'Rose',T.FLOWER_R,T.FLOWER_R,T.FLOWER_R,{solid:false,transparent:true,cross:true,hard:.05}); def(B.FLOWER_Y,'Dandelion',T.FLOWER_Y,T.FLOWER_Y,T.FLOWER_Y,{solid:false,transparent:true,cross:true,hard:.05}); def(B.TALLGRASS,'Tall Grass',T.TALLGRASS,T.TALLGRASS,T.TALLGRASS,{solid:false,transparent:true,cross:true,hard:.05}); D[B.GRASS].icon=T.GRASS_SIDE; D[B.LOG].icon=T.LOG_SIDE; /* ---------------- Texture atlas (procedural pixel art) ---------------- */ const atlas=document.createElement('canvas');atlas.width=atlas.height=256; const A=atlas.getContext('2d'); function tCtx(t){return{ox:(t%16)*16,oy:((t/16)|0)*16};} function px(t,x,y,c){const o=tCtx(t);A.fillStyle=c;A.fillRect(o.ox+x,o.oy+y,1,1);} function hsl(h,s,l,a){return a===undefined?`hsl(${h},${s}%,${l}%)`:`hsla(${h},${s}%,${l}%,${a})`;} function noiseFill(t,h,s,l,v,rng){for(let y=0;y<16;y++)for(let x=0;x<16;x++)px(t,x,y,hsl(h,s,l+(rng()-.5)*v));} function genTextures(){ const R=t=>mulberry32(0xC0FFEE+t*7919); let r; r=R(1);noiseFill(T.GRASS_TOP,100,42,42,16,r); for(let i=0;i<26;i++)px(T.GRASS_TOP,(r()*16)|0,(r()*16)|0,hsl(100,48,30+r()*8)); r=R(2);noiseFill(T.DIRT,28,38,36,14,r); for(let i=0;i<14;i++)px(T.DIRT,(r()*16)|0,(r()*16)|0,hsl(28,30,24)); r=R(3);noiseFill(T.GRASS_SIDE,28,38,36,14,r); for(let y=0;y<3;y++)for(let x=0;x<16;x++)px(T.GRASS_SIDE,x,y,hsl(100,45,40+(r()-.5)*14)); for(let x=0;x<16;x++)if(r()<.6)px(T.GRASS_SIDE,x,3,hsl(100,45,38+(r()-.5)*10)); r=R(4);noiseFill(T.STONE,220,3,47,11,r); for(let i=0;i<7;i++){let x=(r()*13)|0,y=(r()*15)|0,len=2+(r()*4)|0;for(let k=0;k<len;k++)px(T.STONE,x+k,y,hsl(220,3,35));} r=R(5);noiseFill(T.SAND,50,42,73,8,r); for(let i=0;i<10;i++)px(T.SAND,(r()*16)|0,(r()*16)|0,hsl(48,40,62)); r=R(6);for(let y=0;y<16;y++)for(let x=0;x<16;x++)px(T.WATER,x,y,hsl(218,72,40+(r()-.5)*10+((y%4===0&&r()<.5)?9:0),.85)); r=R(7);for(let x=0;x<16;x++){const base=(x%4<2)?31:23;for(let y=0;y<16;y++)px(T.LOG_SIDE,x,y,hsl(30,40,base+(r()-.5)*7));} r=R(8);for(let y=0;y<16;y++)for(let x=0;x<16;x++){const d=Math.max(Math.abs(x-7.5),Math.abs(y-7.5));px(T.LOG_TOP,x,y,hsl(33,42,(d|0)%2?40:28+(r()-.5)*6));} r=R(9);for(let y=0;y<16;y++)for(let x=0;x<16;x++){if(r()<.16)continue;px(T.LEAVES,x,y,hsl(108,48,22+r()*18));} r=R(10);for(let y=0;y<16;y++)for(let x=0;x<16;x++){let l=46+(r()-.5)*8;if(y%4===3)l=30;if((y<4&&x===7)||(y>=4&&y<8&&x===3)||(y>=8&&y<12&&x===11)||(y>=12&&x===5))l=30;px(T.PLANKS,x,y,hsl(33,42,l));} r=R(11);noiseFill(T.COBBLE,220,4,46,20,r); for(let i=0;i<14;i++){let x=(r()*16)|0,y=(r()*16)|0;for(let k=0;k<6;k++){px(T.COBBLE,x&15,y&15,hsl(220,4,24));x+=(r()*3-1)|0;y+=(r()*3-1)|0;if(x<0||y<0||x>15||y>15)break;}} r=R(12);A.fillStyle='rgba(180,220,255,0.10)';const g=tCtx(T.GLASS);A.fillRect(g.ox,g.oy,16,16); for(let i=0;i<16;i++){px(T.GLASS,i,0,hsl(0,0,88,.95));px(T.GLASS,i,15,hsl(0,0,88,.95));px(T.GLASS,0,i,hsl(0,0,88,.95));px(T.GLASS,15,i,hsl(0,0,88,.95));} for(let i=0;i<5;i++){px(T.GLASS,3+i,8-i,hsl(0,0,95,.9));px(T.GLASS,8+i,13-i,hsl(0,0,95,.9));} function ore(t,color){const rr=R(t+40);noiseFill(t,220,3,47,11,rr);for(let i=0;i<5;i++){const x=1+(rr()*13)|0,y=1+(rr()*13)|0;px(t,x,y,color);px(t,x+1,y,color);px(t,x,y+1,color);if(rr()<.6)px(t,x+1,y+1,color);}} ore(T.COAL,'#1c1c1c');ore(T.IRON,hsl(20,45,65));ore(T.GOLD,hsl(48,90,55));ore(T.DIAMOND,hsl(180,80,62)); r=R(13);for(let y=0;y<16;y++)for(let x=0;x<16;x++)px(T.BEDROCK,x,y,hsl(0,0,r()<.5?14+r()*10:34+r()*14)); r=R(14);noiseFill(T.SNOW_TOP,210,12,92,6,r); r=R(15);noiseFill(T.SNOW_SIDE,28,38,36,14,r); for(let y=0;y<4;y++)for(let x=0;x<16;x++)if(y<3||r()<.5)px(T.SNOW_SIDE,x,y,hsl(210,12,90+(r()-.5)*6)); r=R(16);for(let y=0;y<16;y++)for(let x=0;x<16;x++){const c=r();px(T.GRAVEL,x,y,c<.3?hsl(28,12,38):c<.6?hsl(220,4,52):hsl(220,4,40));} r=R(17);for(let y=0;y<16;y++)for(let x=0;x<16;x++){const row=(y/4)|0,off=row%2?4:0,mortar=(y%4===3)||(((x+off)%8)===7);px(T.BRICK,x,y,mortar?hsl(20,8,70):hsl(5,55,38+(r()-.5)*9));} function flower(t,petal,center){const rr=R(t+60);for(let y=8;y<16;y++)px(t,7+(y%3===0?1:0)-(y%5===0?1:0)? 7:7,y,hsl(110,50,30)); for(let y=9;y<16;y++)px(t,7,y,hsl(110,55,28+rr()*8));px(t,6,11,hsl(110,55,30));px(t,8,13,hsl(110,55,30)); const cx=7,cy=5;[[0,0],[1,0],[-1,0],[0,1],[0,-1],[1,1],[-1,-1],[1,-1],[-1,1]].forEach(o=>px(t,cx+o[0],cy+o[1],petal));px(t,cx,cy,center);} flower(T.FLOWER_R,hsl(0,75,48),hsl(0,80,32));flower(T.FLOWER_Y,hsl(52,95,55),hsl(40,95,45)); r=R(18);for(let i=0;i<7;i++){let x=2+i*2,h=5+(r()*6)|0;for(let y=15;y>15-h;y--){px(T.TALLGRASS,x+((y%4===0)?(r()<.5?1:-1):0),y,hsl(105,48,28+r()*16));}} r=R(19);for(let y=0;y<16;y++)for(let x=0;x<16;x++){let l=34+(r()-.5)*8;if(x%4===0)l=24;if(x%4===2&&y%4===1)l=55;px(T.CACTUS_SIDE,x,y,hsl(95,52,l));} r=R(20);for(let y=0;y<16;y++)for(let x=0;x<16;x++){const b=x===0||y===0||x===15||y===15;px(T.CACTUS_TOP,x,y,hsl(95,52,b?26:42+(r()-.5)*8));} r=R(21);for(let y=0;y<16;y++)for(let x=0;x<16;x++){let l=70+(r()-.5)*7;if(y===0||y===15)l=58;if(y>3&&y<12&&r()<.08)l=60;px(T.SANDSTONE,x,y,hsl(48,38,l));} r=R(22);noiseFill(T.GLOW,45,85,55,25,r);for(let i=0;i<9;i++){const x=(r()*14)|0,y=(r()*14)|0;px(T.GLOW,x,y,hsl(48,95,78));px(T.GLOW,x+1,y,hsl(48,95,72));px(T.GLOW,x,y+1,hsl(48,95,72));} for(let s=0;s<8;s++){const t=T.CRACK0+s,rr=mulberry32(777);A.fillStyle='rgba(0,0,0,0.8)'; const cracks=2+s;for(let c=0;c<cracks;c++){let x=4+(rr()*8)|0,y=4+(rr()*8)|0;const steps=3+s*2; for(let k=0;k<steps;k++){const o=tCtx(t);A.fillRect(o.ox+(x&15),o.oy+(y&15),1,1);x+=(rr()*3-1)|0;y+=(rr()*3-1)|0;}}} } /* ---------------- World ---------------- */ const CH=16,H=64,SEA=28; let RD=5; const chunks=new Map(),dirty=new Set(),editsByChunk=new Map(); const ckey=(cx,cz)=>cx+','+cz; const bidx=(x,y,z)=>(x*16+z)*H+y; let worldTime=0;const DAY=480; function biomeAt(x,z){const t=fbm2(x*.0035+900,z*.0035-700,3);if(t>.34)return'desert';if(t<-.42)return'snow';return fbm2(x*.012+33,z*.012-71,3)>.06?'forest':'plains';} function groundH(x,z){ const cont=fbm2(x*.0032,z*.0032,4),hills=fbm2(x*.014+50,z*.014+50,3); let m=fbm2(x*.007+200,z*.007+200,4);m=Math.max(0,m); let h=30+cont*12+hills*5+m*m*38; return clamp(h|0,4,H-10); } function genChunk(cx,cz){ const blocks=new Uint8Array(16*H*16); const hs=new Int16Array(256),bs=[]; for(let lx=0;lx<16;lx++)for(let lz=0;lz<16;lz++){ const wx=cx*16+lx,wz=cz*16+lz,h=groundH(wx,wz),bio=biomeAt(wx,wz); hs[lx*16+lz]=h;bs[lx*16+lz]=bio; for(let y=0;y<H;y++){ let id=B.AIR; if(y===0)id=B.BEDROCK; else if(y<h-3){ id=B.STONE;const r=h3(wx,y,wz); if(r<.0016&&y<14)id=B.DIAMOND;else if(r<.004&&y<22)id=B.GOLD;else if(r<.011&&y<34)id=B.IRON;else if(r<.022&&y<44)id=B.COAL;else if(r>.992)id=B.GRAVEL; }else if(y<h)id=(bio==='desert'||h<=SEA+1)?B.SAND:B.DIRT; else if(y===h){ if(h<=SEA+1)id=B.SAND; else if(bio==='desert')id=B.SAND; else if(bio==='snow')id=B.SNOWGRASS; else id=B.GRASS; }else if(y<=SEA)id=B.WATER; // caves if(id!==B.AIR&&id!==B.BEDROCK&&id!==B.WATER&&y>1){ const canBreach=h>SEA?y<=h:y<h-3; if(canBreach&&perlin.n3(wx*.065,y*.105,wz*.065)>.44)id=B.AIR; } blocks[bidx(lx,y,lz)]=id; } } // decorations const rng=mulberry32((cx*341873128+cz*132897987^SEED)>>>0); function top(lx,lz){for(let y=H-1;y>0;y--){const b=blocks[bidx(lx,y,lz)];if(b!==B.AIR)return y;}return 0;} for(let lx=0;lx<16;lx++)for(let lz=0;lz<16;lz++){ const bio=bs[lx*16+lz],wy=top(lx,lz),tb=blocks[bidx(lx,wy,lz)]; if(wy<=SEA)continue; if((tb===B.GRASS||tb===B.SNOWGRASS)&&lx>=2&&lx<=13&&lz>=2&&lz<=13){ const tc=bio==='forest'?.045:bio==='plains'?.006:bio==='snow'?.015:0; if(rng()<tc){ const th=4+((rng()*3)|0); for(let dy=th-2;dy<=th+1;dy++){const ly=wy+dy;if(ly>=H)break;const rad=dy>th-1?1:2; for(let dx=-rad;dx<=rad;dx++)for(let dz=-rad;dz<=rad;dz++){ if(Math.abs(dx)===rad&&Math.abs(dz)===rad&&rng()<.5)continue; const i=bidx(lx+dx,ly,lz+dz);if(blocks[i]===B.AIR)blocks[i]=B.LEAVES;}} for(let dy=1;dy<=th&&wy+dy<H;dy++)blocks[bidx(lx,wy+dy,lz)]=B.LOG; continue; } } if(tb===B.GRASS&&wy+1<H){ const r=rng(); if(r<.05)blocks[bidx(lx,wy+1,lz)]=B.TALLGRASS; else if(r<.062)blocks[bidx(lx,wy+1,lz)]=rng()<.5?B.FLOWER_R:B.FLOWER_Y; } if(bio==='desert'&&tb===B.SAND&&rng()<.004){ const ch2=2+((rng()*2)|0);for(let dy=1;dy<=ch2&&wy+dy<H;dy++)blocks[bidx(lx,wy+dy,lz)]=B.CACTUS; } } // apply saved edits const ed=editsByChunk.get(ckey(cx,cz)); if(ed)for(const k in ed){const[lx,y,lz]=k.split(',').map(Number);blocks[bidx(lx,y,lz)]=ed[k];} const chunk={cx,cz,blocks,meshO:null,meshW:null}; chunks.set(ckey(cx,cz),chunk); dirty.add(ckey(cx,cz)); [[1,0],[-1,0],[0,1],[0,-1]].forEach(o=>{if(chunks.has(ckey(cx+o[0],cz+o[1])))dirty.add(ckey(cx+o[0],cz+o[1]));}); return chunk; } function getBlock(x,y,z){ if(y<0)return B.BEDROCK;if(y>=H)return B.AIR; const c=chunks.get(ckey(Math.floor(x/16),Math.floor(z/16))); if(!c)return B.AIR; return c.blocks[bidx(((x%16)+16)%16,y,((z%16)+16)%16)]; } function setBlock(x,y,z,id,record=true){ if(y<0||y>=H)return; const cx=Math.floor(x/16),cz=Math.floor(z/16),c=chunks.get(ckey(cx,cz)); if(!c)return; const lx=((x%16)+16)%16,lz=((z%16)+16)%16; c.blocks[bidx(lx,y,lz)]=id; if(record){let ed=editsByChunk.get(ckey(cx,cz));if(!ed){ed={};editsByChunk.set(ckey(cx,cz),ed);}ed[lx+','+y+','+lz]=id;} dirty.add(ckey(cx,cz)); if(lx===0)dirty.add(ckey(cx-1,cz));if(lx===15)dirty.add(ckey(cx+1,cz)); if(lz===0)dirty.add(ckey(cx,cz-1));if(lz===15)dirty.add(ckey(cx,cz+1)); } /* ---------------- Meshing ---------------- */ const FACES=[ {dir:[-1,0,0],corners:[{pos:[0,1,0],uv:[0,1]},{pos:[0,0,0],uv:[0,0]},{pos:[0,1,1],uv:[1,1]},{pos:[0,0,1],uv:[1,0]}],shade:.6}, {dir:[1,0,0], corners:[{pos:[1,1,1],uv:[0,1]},{pos:[1,0,1],uv:[0,0]},{pos:[1,1,0],uv:[1,1]},{pos:[1,0,0],uv:[1,0]}],shade:.6}, {dir:[0,-1,0],corners:[{pos:[1,0,1],uv:[1,0]},{pos:[0,0,1],uv:[0,0]},{pos:[1,0,0],uv:[1,1]},{pos:[0,0,0],uv:[0,1]}],shade:.5}, {dir:[0,1,0], corners:[{pos:[0,1,1],uv:[1,1]},{pos:[1,1,1],uv:[0,1]},{pos:[0,1,0],uv:[1,0]},{pos:[1,1,0],uv:[0,0]}],shade:1}, {dir:[0,0,-1],corners:[{pos:[1,0,0],uv:[0,0]},{pos:[0,0,0],uv:[1,0]},{pos:[1,1,0],uv:[0,1]},{pos:[0,1,0],uv:[1,1]}],shade:.8}, {dir:[0,0,1], corners:[{pos:[0,0,1],uv:[0,0]},{pos:[1,0,1],uv:[1,0]},{pos:[0,1,1],uv:[0,1]},{pos:[1,1,1],uv:[1,1]}],shade:.8}]; const TS=1/16,PAD=.6/256; const AOF=[.45,.62,.8,1]; let matOpaque,matWater,atlasTex; function tileUV(t,ux,uy){const col=t%16,row=(t/16)|0;return[col*TS+PAD+ux*(TS-2*PAD),1-(row+1)*TS+PAD+uy*(TS-2*PAD)];} function meshChunk(c){ if(c.meshO){scene.remove(c.meshO);c.meshO.geometry.dispose();c.meshO=null;} if(c.meshW){scene.remove(c.meshW);c.meshW.geometry.dispose();c.meshW=null;} const pO=[],uO=[],cO=[],iO=[],pW=[],uW=[],cW=[],iW=[]; const ox=c.cx*16,oz=c.cz*16; function gb(x,y,z){ if(y<0)return B.BEDROCK;if(y>=H)return B.AIR; const cc=chunks.get(ckey(Math.floor(x/16),Math.floor(z/16))); if(!cc)return B.STONE; return cc.blocks[bidx(((x%16)+16)%16,y,((z%16)+16)%16)]; } const occ=(x,y,z)=>{const b=gb(x,y,z),d=D[b];return b!==B.AIR&&d.solid&&!d.transparent;}; for(let lx=0;lx<16;lx++)for(let lz=0;lz<16;lz++)for(let y=0;y<H;y++){ const id=c.blocks[bidx(lx,y,lz)];if(id===B.AIR)continue; const dd=D[id],wx=ox+lx,wz=oz+lz; if(dd.cross){ // X-shaped plant const t=dd.side,quads=[[[.15,0,.15],[.85,0,.85],[.15,1,.15],[.85,1,.85]],[[.85,0,.15],[.15,0,.85],[.85,1,.15],[.15,1,.85]]]; for(const q of quads)for(const flip of[0,1]){ const n=pO.length/3; const ord=flip?[1,0,3,2]:[0,1,2,3]; const uvs=[[0,0],[1,0],[0,1],[1,1]]; for(let k=0;k<4;k++){const v=q[ord[k]];pO.push(lx+v[0],y+v[1],lz+v[2]);const u=tileUV(t,uvs[k][0],uvs[k][1]);uO.push(u[0],u[1]);cO.push(.95,.95,.95);} iO.push(n,n+1,n+2,n+2,n+1,n+3); } continue; } const isW=id===B.WATER; const topY=(isW&&gb(wx,y+1,wz)!==B.WATER)?.875:1; for(let f=0;f<6;f++){ const F=FACES[f],nx=wx+F.dir[0],ny=y+F.dir[1],nz=wz+F.dir[2]; const nid=gb(nx,ny,nz),nd=D[nid]; const visible=nid===B.AIR||nd.cross||(nd.transparent&&(nid!==id||!dd.cullSame)); if(!visible)continue; const tile=F.dir[1]===1?dd.top:F.dir[1]===-1?dd.bottom:dd.side; const[P,U,C,I]=isW?[pW,uW,cW,iW]:[pO,uO,cO,iO]; const n=P.length/3,ao=[1,1,1,1]; const a=F.dir[0]?0:F.dir[1]?1:2,p1=(a+1)%3,p2=(a+2)%3; for(let k=0;k<4;k++){ const cr=F.corners[k]; let yy=cr.pos[1]===1?topY:cr.pos[1]; P.push(lx+cr.pos[0],y+yy,lz+cr.pos[2]); const u=tileUV(tile,cr.uv[0],cr.uv[1]);U.push(u[0],u[1]); let aoV=1; if(!isW){ const bp=[nx,ny,nz],s=[0,0,0],t2=[0,0,0]; s[p1]=cr.pos[p1]===1?1:-1;t2[p2]=cr.pos[p2]===1?1:-1; const s1=occ(bp[0]+s[0],bp[1]+s[1],bp[2]+s[2])?1:0; const s2=occ(bp[0]+t2[0],bp[1]+t2[1],bp[2]+t2[2])?1:0; const co=occ(bp[0]+s[0]+t2[0],bp[1]+s[1]+t2[1],bp[2]+s[2]+t2[2])?1:0; aoV=AOF[(s1&&s2)?0:3-s1-s2-co]; } ao[k]=aoV;const sh=F.shade*aoV;C.push(sh,sh,sh); } if(ao[0]+ao[3]<ao[1]+ao[2])I.push(n,n+1,n+3,n,n+3,n+2); else I.push(n,n+1,n+2,n+2,n+1,n+3); } } function build(pos,uv,col,idx,mat,ro){ if(!idx.length)return null; const g=new THREE.BufferGeometry(); g.setAttribute('position',new THREE.Float32BufferAttribute(pos,3)); g.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2)); g.setAttribute('color',new THREE.Float32BufferAttribute(col,3)); g.setIndex(idx); const m=new THREE.Mesh(g,mat); m.position.set(ox,0,oz);m.matrixAutoUpdate=false;m.updateMatrix();m.renderOrder=ro; scene.add(m);return m; } c.meshO=build(pO,uO,cO,iO,matOpaque,0); c.meshW=build(pW,uW,cW,iW,matWater,2); } /* ---------------- Three.js setup ---------------- */ let scene,camera,renderer,sunLight,ambLight,skyPivot,sunMesh,moonMesh,stars,cloudGroup; let highlight,crackMesh,handGroup,handMesh; function setupScene(){ scene=new THREE.Scene(); scene.background=new THREE.Color(0x87b1ff); scene.fog=new THREE.Fog(0x87b1ff,RD*16*.55,RD*16*.95); camera=new THREE.PerspectiveCamera(75,innerWidth/innerHeight,.1,1000); camera.rotation.order='YXZ';scene.add(camera); renderer=new THREE.WebGLRenderer({antialias:false}); renderer.setPixelRatio(Math.min(devicePixelRatio,1.5)); renderer.setSize(innerWidth,innerHeight); renderer.domElement.id='gameCanvas'; document.body.appendChild(renderer.domElement); atlasTex=new THREE.CanvasTexture(atlas); atlasTex.magFilter=atlasTex.minFilter=THREE.NearestFilter;atlasTex.generateMipmaps=false; matOpaque=new THREE.MeshBasicMaterial({map:atlasTex,vertexColors:true,alphaTest:.5,side:THREE.FrontSide}); matWater=new THREE.MeshBasicMaterial({map:atlasTex,vertexColors:true,transparent:true,opacity:.78,depthWrite:false,side:THREE.DoubleSide}); ambLight=new THREE.AmbientLight(0xffffff,.7);scene.add(ambLight); sunLight=new THREE.DirectionalLight(0xffffff,.7);scene.add(sunLight);scene.add(sunLight.target); // sky skyPivot=new THREE.Group();scene.add(skyPivot); const sg=new THREE.PlaneGeometry(46,46); sunMesh=new THREE.Mesh(sg,new THREE.MeshBasicMaterial({color:0xffe14d,fog:false})); sunMesh.position.set(420,0,0);skyPivot.add(sunMesh); moonMesh=new THREE.Mesh(new THREE.PlaneGeometry(30,30),new THREE.MeshBasicMaterial({color:0xd8dce8,fog:false})); moonMesh.position.set(-420,0,0);skyPivot.add(moonMesh); const starPos=[];const srng=mulberry32(42); for(let i=0;i<450;i++){const v=new THREE.Vector3(srng()*2-1,srng()*2-1,srng()*2-1).normalize().multiplyScalar(400);starPos.push(v.x,v.y,v.z);} const stg=new THREE.BufferGeometry();stg.setAttribute('position',new THREE.Float32BufferAttribute(starPos,3)); stars=new THREE.Points(stg,new THREE.PointsMaterial({color:0xffffff,size:1.6,fog:false,transparent:true,opacity:0})); skyPivot.add(stars); // clouds cloudGroup=new THREE.Group();scene.add(cloudGroup); const crng=mulberry32(7); for(let i=0;i<26;i++){ const m=new THREE.Mesh(new THREE.BoxGeometry(1,1,1),new THREE.MeshBasicMaterial({color:0xffffff,transparent:true,opacity:.5})); m.scale.set(12+crng()*26,3.2,10+crng()*20); m.position.set((crng()-.5)*420,70+crng()*8,(crng()-.5)*420); cloudGroup.add(m); } // block highlight + crack overlay highlight=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.002,1.002,1.002)),new THREE.LineBasicMaterial({color:0x000000,transparent:true,opacity:.7})); highlight.visible=false;scene.add(highlight); crackMesh=new THREE.Mesh(new THREE.BoxGeometry(1.004,1.004,1.004),new THREE.MeshBasicMaterial({map:atlasTex,transparent:true,depthWrite:false,polygonOffset:true,polygonOffsetFactor:-2})); crackMesh.visible=false;crackMesh.renderOrder=1;scene.add(crackMesh); // held block handGroup=new THREE.Group();camera.add(handGroup); handMesh=new THREE.Mesh(new THREE.BoxGeometry(.35,.35,.35),matOpaque); handMesh.position.set(.42,-.42,-.65);handMesh.rotation.set(.2,Math.PI/5,0); handGroup.add(handMesh); addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);}); } function setCubeUV(geom,tiles){ // tiles: [px,nx,py,ny,pz,nz] const uv=geom.attributes.uv; for(let face=0;face<6;face++){const t=tiles[face]; for(let v=0;v<4;v++){const i=face*4+v; const u0=tileUV(t,uv.getX(i)>.5?1:0,uv.getY(i)>.5?1:0); uv.setXY(i,u0[0],u0[1]);}} uv.needsUpdate=true; } function updateHandMesh(){ const id=hotbar[hotSel],dd=D[id]; setCubeUV(handMesh.geometry,[dd.side,dd.side,dd.top,dd.bottom,dd.side,dd.side]); } function setCrackStage(s){setCubeUV(crackMesh.geometry,Array(6).fill(T.CRACK0+clamp(s,0,7)));} /* ---------------- Audio ---------------- */ let AC=null; function ac(){if(!AC)AC=new(window.AudioContext||window.webkitAudioContext)();if(AC.state==='suspended')AC.resume();return AC;} function sfx(f1,f2,dur,type,vol){ try{const a=ac(),o=a.createOscillator(),g=a.createGain(); o.type=type||'square';o.frequency.setValueAtTime(f1,a.currentTime); o.frequency.exponentialRampToValueAtTime(Math.max(20,f2||f1),a.currentTime+dur); g.gain.setValueAtTime(vol||.15,a.currentTime); g.gain.exponentialRampToValueAtTime(.001,a.currentTime+dur); o.connect(g).connect(a.destination);o.start();o.stop(a.currentTime+dur);}catch(e){} } const sndDig=id=>{const d=D[id];if(id===B.STONE||id===B.COBBLE||d.hard>1.2)sfx(95,55,.12,'square',.18);else if(id===B.SAND||id===B.GRAVEL)sfx(180,90,.1,'triangle',.2);else sfx(150,80,.1,'triangle',.18);}; const sndPlace=()=>sfx(220,140,.08,'square',.14); const sndHurt=()=>sfx(280,90,.25,'sawtooth',.2); const sndPop=()=>sfx(400,900,.12,'square',.15); /* ---------------- Particles ---------------- */ const particles=[];const tileColorCache={}; function tileColor(t){ if(tileColorCache[t])return tileColorCache[t]; const o=tCtx(t),d=A.getImageData(o.ox,o.oy,16,16).data; let r=0,g=0,b=0,n=0; for(let i=0;i<d.length;i+=4)if(d[i+3]>100){r+=d[i];g+=d[i+1];b+=d[i+2];n++;} const c=n?new THREE.Color(r/n/255,g/n/255,b/n/255):new THREE.Color(.5,.5,.5); return tileColorCache[t]=c; } const partGeo=new THREE.BoxGeometry(.1,.1,.1);const partMats={}; function spawnParticles(x,y,z,tile,count){ const c=tileColor(tile),key=c.getHexString(); if(!partMats[key])partMats[key]=new THREE.MeshBasicMaterial({color:c}); for(let i=0;i<count;i++){ const m=new THREE.Mesh(partGeo,partMats[key]); m.position.set(x+Math.random(),y+Math.random(),z+Math.random()); scene.add(m); particles.push({m,vx:(Math.random()-.5)*4,vy:2+Math.random()*3,vz:(Math.random()-.5)*4,life:.5+Math.random()*.3}); } } function updateParticles(dt){ for(let i=particles.length-1;i>=0;i--){const p=particles[i]; p.life-=dt;p.vy-=18*dt; p.m.position.x+=p.vx*dt;p.m.position.y+=p.vy*dt;p.m.position.z+=p.vz*dt; if(p.life<=0){scene.remove(p.m);particles.splice(i,1);}} } /* ---------------- Mobs ---------------- */ const mobs=[]; function lambBox(w,h,d,color,x,y,z,parent,pivotTop){ const g=new THREE.BoxGeometry(w,h,d); if(pivotTop)g.translate(0,-h/2,0); const m=new THREE.Mesh(g,new THREE.MeshLambertMaterial({color})); m.position.set(x,y,z);parent.add(m);return m; } class Mob{ constructor(type,x,y,z){ this.type=type;this.x=x;this.y=y;this.z=z;this.vy=0;this.dir=Math.random()*Math.PI*2; this.state='idle';this.timer=1+Math.random()*3;this.animT=0;this.onGround=false; this.attackCd=0;this.legs=[];this.g=new THREE.Group(); if(type==='pig'){this.hp=10;this.speed=1.2; const c=0xeb9c9c;lambBox(.62,.5,.95,c,0,.62,0,this.g); const head=lambBox(.48,.48,.42,0xf0a8a8,0,.72,.62,this.g); lambBox(.24,.16,.06,0xd87f7f,0,-.04,.24,head); lambBox(.07,.07,.02,0x202020,-.13,.1,.22,head);lambBox(.07,.07,.02,0x202020,.13,.1,.22,head); [[-.2,-.32],[.2,-.32],[-.2,.32],[.2,.32]].forEach(o=>this.legs.push(lambBox(.18,.38,.18,0xdb8e8e,o[0],.38,o[1],this.g,true))); }else if(type==='sheep'){this.hp=8;this.speed=1; lambBox(.7,.62,1.05,0xe8e8e8,0,.78,0,this.g); const head=lambBox(.4,.4,.35,0xd8c5b8,0,.95,.65,this.g); lambBox(.06,.06,.02,0x202020,-.1,.05,.18,head);lambBox(.06,.06,.02,0x202020,.1,.05,.18,head); [[-.22,-.35],[.22,-.35],[-.22,.35],[.22,.35]].forEach(o=>this.legs.push(lambBox(.17,.48,.17,0xcfcfcf,o[0],.48,o[1],this.g,true))); }else{ // zombie this.hp=20;this.speed=1.7; const skin=0x57a04b; [[-.13,0],[.13,0]].forEach(o=>this.legs.push(lambBox(.22,.72,.22,0x2e6b8a,o[0],.72,o[1],this.g,true))); lambBox(.52,.68,.3,0x3a7ca5,0,1.06,0,this.g); const head=lambBox(.48,.48,.48,skin,0,1.64,0,this.g); lambBox(.08,.08,.02,0x111111,-.11,.05,.25,head);lambBox(.08,.08,.02,0x111111,.11,.05,.25,head); this.arms=[lambBox(.18,.18,.62,skin,-.35,1.28,.28,this.g),lambBox(.18,.18,.62,skin,.35,1.28,.28,this.g)]; } this.g.position.set(x,y,z);scene.add(this.g); } solidAt(x,y,z){const b=getBlock(Math.floor(x),Math.floor(y),Math.floor(z));return D[b].solid;} damage(n,kx,kz){ this.hp-=n;this.vy=5;this.x+=kx*.3;this.z+=kz*.3; this.g.traverse(o=>{if(o.material)o.material.emissive=new THREE.Color(0x880000);}); setTimeout(()=>this.g.traverse(o=>{if(o.material)o.material.emissive=new THREE.Color(0)}),140); sfx(200,80,.15,'sawtooth',.15); if(this.hp<=0){ spawnParticles(this.x-.5,this.y+.4,this.z-.5,this.type==='zombie'?T.LEAVES:T.FLOWER_R,14); sndPop();this.dead=true; } } update(dt){ this.timer-=dt;this.attackCd-=dt; const dx=player.x-this.x,dz=player.z-this.z,distP=Math.hypot(dx,dz); if(this.type==='zombie'&&distP<14){this.state='walk';this.dir=Math.atan2(dx,dz); if(distP<1.3&&this.attackCd<=0&&Math.abs(player.y-this.y)<2){this.attackCd=1;hurt(3,dx/distP,dz/distP);} }else if(this.timer<=0){ this.timer=1.5+Math.random()*4; this.state=Math.random()<.55?'walk':'idle'; if(this.state==='walk')this.dir=Math.random()*Math.PI*2; } let spd=this.state==='walk'?this.speed:0; if(this.type==='zombie'&&distP<14)spd=2.4; if(spd>0){ const fx=Math.sin(this.dir),fz=Math.cos(this.dir); const nx=this.x+fx*spd*dt,nz=this.z+fz*spd*dt; const lx=nx+fx*.35,lz=nz+fz*.35; const blocked=this.solidAt(lx,this.y+.1,lz)||this.solidAt(lx,this.y+1.1,lz); if(blocked){ if(this.onGround&&!this.solidAt(lx,this.y+1.4,lz)&&!this.solidAt(this.x,this.y+2.2,this.z))this.vy=7; else this.dir+=Math.PI+(Math.random()-.5); }else{this.x=nx;this.z=nz;} this.animT+=dt*spd*3.4; } this.vy-=22*dt; const inW=D[getBlock(Math.floor(this.x),Math.floor(this.y+.3),Math.floor(this.z))].liquid; if(inW){this.vy=Math.max(this.vy,-1);this.vy+=26*dt;this.vy=Math.min(this.vy,2.2);} this.y+=this.vy*dt;this.onGround=false; if(this.vy<=0&&this.solidAt(this.x,this.y,this.z)){this.y=Math.floor(this.y)+1;this.vy=0;this.onGround=true;} if(this.vy>0&&this.solidAt(this.x,this.y+1.8,this.z))this.vy=0; if(this.y<-10)this.dead=true; const sw=Math.sin(this.animT)*.7; this.legs.forEach((l,i)=>l.rotation.x=i%2?sw:-sw); this.g.position.set(this.x,this.y,this.z); this.g.rotation.y=this.dir; if(distP>70)this.dead=true; if(this.type==='zombie'&&dayFactor>.6&&Math.random()<dt*.4){spawnParticles(this.x-.5,this.y+1.5,this.z-.5,T.GLOW,3);this.dead=true;} } } function topSolidY(x,z){ if(!chunks.has(ckey(Math.floor(x/16),Math.floor(z/16))))return-1; for(let y=H-1;y>0;y--){const b=getBlock(x,y,z);if(b===B.WATER)return-1;if(D[b].solid)return y;} return-1; } let mobTimer=0; function updateMobs(dt){ mobTimer-=dt; if(mobTimer<=0){ mobTimer=1.5; const night=sunElev<-.05; const zCount=mobs.filter(m=>m.type==='zombie').length; const pCount=mobs.length-zCount; const ang=Math.random()*Math.PI*2,r=18+Math.random()*22; const x=Math.floor(player.x+Math.sin(ang)*r),z=Math.floor(player.z+Math.cos(ang)*r); const y=topSolidY(x,z); if(y>0&&y<H-3){ const tb=getBlock(x,y,z); if((tb===B.GRASS||tb===B.SAND||tb===B.SNOWGRASS)&&!D[getBlock(x,y+1,z)].solid){ if(night&&zCount<8&&Math.random()<.75)mobs.push(new Mob('zombie',x+.5,y+1,z+.5)); else if(pCount<10)mobs.push(new Mob(Math.random()<.5?'pig':'sheep',x+.5,y+1,z+.5)); } } } for(let i=mobs.length-1;i>=0;i--){ const m=mobs[i];m.update(dt); if(m.dead){scene.remove(m.g);mobs.splice(i,1);} } } /* ---------------- Player ---------------- */ const player={x:8,y:40,z:8,vx:0,vy:0,vz:0,yaw:0,pitch:0,onGround:false,fly:false,hp:20,peakY:0,bobT:0,sneak:false,sprint:false,inWater:false}; let spawnPoint={x:8,y:40,z:8}; const keys={};let dead=false,invOpen=false,playing=false; let lastHurtT=-99,regenT=0,lastWTap=0; const HALF=.3,PH=1.8,EYE=1.62; function collides(){ const x0=Math.floor(player.x-HALF),x1=Math.floor(player.x+HALF); const y0=Math.floor(player.y),y1=Math.floor(player.y+PH); const z0=Math.floor(player.z-HALF),z1=Math.floor(player.z+HALF); for(let x=x0;x<=x1;x++)for(let y=y0;y<=y1;y++)for(let z=z0;z<=z1;z++) if(D[getBlock(x,y,z)].solid)return true; return false; } function hurt(n,kx,kz){ if(dead||n<=0)return; player.hp-=n;lastHurtT=worldTime; if(kx!==undefined){player.vx+=kx*7;player.vz+=kz*7;player.vy=Math.max(player.vy,4.5);} sndHurt(); const f=$('damageFlash');f.style.transition='none';f.style.opacity=.5; requestAnimationFrame(()=>{f.style.transition='opacity .4s';f.style.opacity=0;}); updateHearts(); if(player.hp<=0){dead=true;document.exitPointerLock();$('deathScreen').style.display='flex';} } function updatePlayer(dt){ const steps=Math.max(1,Math.ceil(dt/.0333));const sdt=dt/steps; for(let s=0;s<steps;s++)stepPlayer(sdt); // camera const sneakOff=player.sneak&&player.onGround?-.15:0; let bobO=0; const hSpeed=Math.hypot(player.vx,player.vz); if(player.onGround&&hSpeed>.5){player.bobT+=dt*hSpeed*1.7;bobO=Math.sin(player.bobT*4)*.05;} camera.position.set(player.x,player.y+EYE+sneakOff+bobO,player.z); camera.rotation.set(player.pitch,player.yaw,0); const tFov=player.sprint&&hSpeed>4?84:75; camera.fov=lerp(camera.fov,tFov,dt*8);camera.updateProjectionMatrix(); // water check const eyeB=getBlock(Math.floor(player.x),Math.floor(player.y+EYE+sneakOff),Math.floor(player.z)); $('waterOverlay').style.display=D[eyeB].liquid?'block':'none'; if(D[eyeB].liquid){scene.fog.near=2;scene.fog.far=24;} // regen regenT+=dt; if(regenT>3){regenT=0;if(player.hp<20&&worldTime-lastHurtT>6){player.hp++;updateHearts();}} // hand swing if(swingT>0){swingT-=dt*5;handGroup.rotation.x=-Math.sin(Math.max(0,swingT)*Math.PI)*.7;handGroup.position.y=-Math.sin(Math.max(0,swingT)*Math.PI)*.15;} } function stepPlayer(dt){ const fwd=[-Math.sin(player.yaw),-Math.cos(player.yaw)],right=[Math.cos(player.yaw),-Math.sin(player.yaw)]; let mx=0,mz=0; if(keys.KeyW){mx+=fwd[0];mz+=fwd[1];} if(keys.KeyS){mx-=fwd[0];mz-=fwd[1];} if(keys.KeyD){mx+=right[0];mz+=right[1];} if(keys.KeyA){mx-=right[0];mz-=right[1];} const ml=Math.hypot(mx,mz);if(ml>0){mx/=ml;mz/=ml;} player.sneak=!!keys.ShiftLeft&&!player.fly; if(!keys.KeyW)player.sprint=false; const feetB=D[getBlock(Math.floor(player.x),Math.floor(player.y+.2),Math.floor(player.z))]; const bodyB=D[getBlock(Math.floor(player.x),Math.floor(player.y+1),Math.floor(player.z))]; player.inWater=feetB.liquid||bodyB.liquid; let speed=player.fly?11:player.inWater?2.6:player.sneak?1.4:player.sprint?5.6:4.3; const acc=player.fly?40:(player.onGround?55:12); player.vx+=clamp(mx*speed-player.vx,-acc*dt,acc*dt); player.vz+=clamp(mz*speed-player.vz,-acc*dt,acc*dt); if(player.fly){ let ty=0;if(keys.Space)ty=9;if(keys.ShiftLeft)ty=-9; player.vy+=clamp(ty-player.vy,-50*dt,50*dt); }else if(player.inWater){ player.vy-=8*dt;player.vy=Math.max(player.vy,-2.6); if(keys.Space)player.vy+=clamp(3.2-player.vy,0,30*dt); player.peakY=player.y; }else{ player.vy-=26*dt;player.vy=Math.max(player.vy,-50); if(keys.Space&&player.onGround){player.vy=8.2;player.onGround=false;} } // Y player.y+=player.vy*dt; const wasFalling=player.vy<0; if(collides()){ if(player.vy<0){player.y=Math.floor(player.y)+1+1e-4; if(wasFalling){player.onGround=true; const fall=player.peakY-player.y; if(fall>3.5&&!player.inWater&&!player.fly)hurt(Math.floor(fall-3)); player.peakY=player.y;} }else player.y=Math.floor(player.y+PH)-PH-1e-4; player.vy=0; }else{player.onGround=false;} if(player.onGround||player.fly)player.peakY=player.y; else player.peakY=Math.max(player.peakY,player.y); // X player.x+=player.vx*dt; if(collides()){ if(player.vx>0)player.x=Math.floor(player.x+HALF)-HALF-1e-4; else player.x=Math.floor(player.x-HALF)+1+HALF+1e-4; player.vx=0; } // Z player.z+=player.vz*dt; if(collides()){ if(player.vz>0)player.z=Math.floor(player.z+HALF)-HALF-1e-4; else player.z=Math.floor(player.z-HALF)+1+HALF+1e-4; player.vz=0; } if(player.y<-12){hurt(100);} } /* ---------------- Raycasting + block interaction ---------------- */ function raycast(maxD){ const o=camera.position,d=camera.getWorldDirection(new THREE.Vector3()); let x=Math.floor(o.x),y=Math.floor(o.y),z=Math.floor(o.z); const stX=d.x>0?1:-1,stY=d.y>0?1:-1,stZ=d.z>0?1:-1; const tdX=Math.abs(1/d.x),tdY=Math.abs(1/d.y),tdZ=Math.abs(1/d.z); let tmX=d.x!==0?((x+(stX>0?1:0))-o.x)/d.x:1e30; let tmY=d.y!==0?((y+(stY>0?1:0))-o.y)/d.y:1e30; let tmZ=d.z!==0?((z+(stZ>0?1:0))-o.z)/d.z:1e30; let nx=0,ny=0,nz=0,t=0; for(let i=0;i<120;i++){ const b=getBlock(x,y,z),dd=D[b]; if(b!==B.AIR&&(dd.solid||dd.cross))return{x,y,z,nx,ny,nz,id:b,t}; if(tmX<tmY&&tmX<tmZ){x+=stX;t=tmX;tmX+=tdX;nx=-stX;ny=0;nz=0;} else if(tmY<tmZ){y+=stY;t=tmY;tmY+=tdY;nx=0;ny=-stY;nz=0;} else{z+=stZ;t=tmZ;tmZ+=tdZ;nx=0;ny=0;nz=-stZ;} if(t>maxD)return null; } return null; } let mouseL=false,mouseR=false,breakTarget=null,breakProgress=0,placeTimer=0,swingT=0; function tryHitMob(){ const o=camera.position,d=camera.getWorldDirection(new THREE.Vector3()); let best=null,bestT=4.2; for(const m of mobs){ const c=new THREE.Vector3(m.x,m.y+.9,m.z).sub(o); const t=c.dot(d); if(t>0&&t<bestT){ const perp=c.clone().addScaledVector(d,-t).length(); if(perp<.85){best=m;bestT=t;} } } if(best){ const dx=best.x-player.x,dz=best.z-player.z,l=Math.hypot(dx,dz)||1; best.damage(5,dx/l,dz/l);return true; } return false; } function updateBreaking(dt){ const hit=raycast(5); if(hit){highlight.visible=true;highlight.position.set(hit.x+.5,hit.y+.5,hit.z+.5);} else highlight.visible=false; if(mouseL&&hit&&!invOpen){ const dd=D[hit.id]; if(dd.hard>=0){ const same=breakTarget&&breakTarget.x===hit.x&&breakTarget.y===hit.y&&breakTarget.z===hit.z; if(!same){breakTarget={x:hit.x,y:hit.y,z:hit.z};breakProgress=0;} breakProgress+=dt/Math.max(.05,dd.hard); swingT=1; if(breakProgress>=1){ setBlock(hit.x,hit.y,hit.z,B.AIR); spawnParticles(hit.x,hit.y,hit.z,dd.icon,12); sndDig(hit.id); breakTarget=null;breakProgress=0; } } }else{breakTarget=null;breakProgress=0;} if(breakTarget&&breakProgress>0){ crackMesh.visible=true; crackMesh.position.set(breakTarget.x+.5,breakTarget.y+.5,breakTarget.z+.5); setCrackStage(Math.floor(breakProgress*8)); }else crackMesh.visible=false; placeTimer-=dt; if(mouseR&&!invOpen&&placeTimer<=0&&hit){ placeTimer=.22; const px2=hit.x+hit.nx,py2=hit.y+hit.ny,pz2=hit.z+hit.nz; const cur=getBlock(px2,py2,pz2); if(cur===B.AIR||cur===B.WATER||cur===B.TALLGRASS){ const id=hotbar[hotSel],dd=D[id]; let blocked=false; if(dd.solid){ const ox=Math.abs(player.x-(px2+.5)),oz=Math.abs(player.z-(pz2+.5)); if(ox<HALF+.5&&oz<HALF+.5&&player.y+PH>py2&&player.y<py2+1)blocked=true; } if(!blocked){setBlock(px2,py2,pz2,id);sndPlace();swingT=1;} } } } /* ---------------- Day/Night ---------------- */ let sunElev=1,dayFactor=1; const colNight=new THREE.Color(.04,.05,.12),colDay=new THREE.Color(.49,.66,1),colSet=new THREE.Color(1,.55,.28); function updateDayNight(dt){ worldTime+=dt; const tod=(worldTime/DAY)%1; const ang=tod*Math.PI*2; sunElev=Math.sin(ang); dayFactor=smooth(-.08,.16,sunElev); skyPivot.position.copy(camera.position); skyPivot.rotation.z=ang; sunMesh.lookAt(camera.position);moonMesh.lookAt(camera.position); stars.material.opacity=1-dayFactor; const sky=colNight.clone().lerp(colDay,dayFactor); const sunsetF=clamp(1-Math.abs(sunElev)*5,0,1)*.55; sky.lerp(colSet,sunsetF); scene.background.copy(sky);scene.fog.color.copy(sky); scene.fog.near=RD*16*.55;scene.fog.far=RD*16*.95; const bright=.28+.72*dayFactor; matOpaque.color.setScalar(bright);matWater.color.setScalar(bright); ambLight.intensity=.35+.45*dayFactor; sunLight.intensity=.15+.65*dayFactor; const sd=new THREE.Vector3(Math.cos(ang),Math.abs(Math.sin(ang))*.8+.2,.3).normalize(); sunLight.position.copy(camera.position).addScaledVector(sd,80); sunLight.target.position.copy(camera.position); // clouds drift cloudGroup.children.forEach(c=>{ c.position.x+=1.4*dt; if(c.position.x-player.x>240)c.position.x-=480; if(c.position.x-player.x<-240)c.position.x+=480; if(c.position.z-player.z>240)c.position.z-=480; if(c.position.z-player.z<-240)c.position.z+=480; c.material.opacity=.18+.34*dayFactor; }); } /* ---------------- Chunk streaming ---------------- */ function updateStream(){ const pcx=Math.floor(player.x/16),pcz=Math.floor(player.z/16); const want=[]; for(let dx=-RD;dx<=RD;dx++)for(let dz=-RD;dz<=RD;dz++){ const cx=pcx+dx,cz=pcz+dz; if(!chunks.has(ckey(cx,cz)))want.push([cx,cz,dx*dx+dz*dz]); } want.sort((a,b)=>a[2]-b[2]); for(let i=0;i<Math.min(2,want.length);i++)genChunk(want[i][0],want[i][1]); if(dirty.size){ const list=[...dirty].map(k=>{const[cx,cz]=k.split(',').map(Number);return[k,(cx-pcx)**2+(cz-pcz)**2];}).sort((a,b)=>a[1]-b[1]); let n=0; for(const[k]of list){ const c=chunks.get(k);dirty.delete(k); if(c){meshChunk(c);if(++n>=3)break;} } } if(frame%180===0){ for(const[k,c]of chunks){ const[cx,cz]=k.split(',').map(Number); if(Math.abs(cx-pcx)>RD+2||Math.abs(cz-pcz)>RD+2){ if(c.meshO){scene.remove(c.meshO);c.meshO.geometry.dispose();} if(c.meshW){scene.remove(c.meshW);c.meshW.geometry.dispose();} chunks.delete(k); } } } } /* ---------------- UI ---------------- */ let hotbar=[B.GRASS,B.DIRT,B.STONE,B.LOG,B.PLANKS,B.COBBLE,B.GLASS,B.SAND,B.BRICK],hotSel=0; const INV_ITEMS=[B.GRASS,B.DIRT,B.STONE,B.COBBLE,B.PLANKS,B.LOG,B.LEAVES,B.SAND,B.SANDSTONE,B.GRAVEL,B.BRICK,B.GLASS,B.GLOW,B.SNOWGRASS,B.SNOW,B.COAL,B.IRON,B.GOLD,B.DIAMOND,B.CACTUS,B.FLOWER_R,B.FLOWER_Y,B.TALLGRASS,B.WATER,B.BEDROCK]; function drawIcon(cv,id){ const ctx=cv.getContext('2d');ctx.imageSmoothingEnabled=false; ctx.clearRect(0,0,cv.width,cv.height); const t=D[id].icon,o=tCtx(t); ctx.drawImage(atlas,o.ox,o.oy,16,16,0,0,cv.width,cv.height); } function buildHotbar(){ const hb=$('hotbar');hb.innerHTML=''; for(let i=0;i<9;i++){ const s=document.createElement('div');s.className='slot'+(i===hotSel?' sel':''); const cv=document.createElement('canvas');cv.width=cv.height=32;cv.className='pixel'; drawIcon(cv,hotbar[i]);s.appendChild(cv);hb.appendChild(s); } } function selectSlot(i){ hotSel=((i%9)+9)%9; document.querySelectorAll('#hotbar .slot').forEach((s,k)=>s.classList.toggle('sel',k===hotSel)); updateHandMesh();showItemName(); } let nameTimer; function showItemName(){ const el=$('itemname');el.textContent=D[hotbar[hotSel]].name;el.style.opacity=1; clearTimeout(nameTimer);nameTimer=setTimeout(()=>el.style.opacity=0,1300); } function updateHearts(){ const n=Math.ceil(clamp(player.hp,0,20)/2);let s=''; for(let i=0;i<10;i++)s+=`<span style="color:${i<n?'#e3340b':'#3a3a3a'}">♥</span>`; $('hearts').innerHTML=s; } function buildInventory(){ const g=$('invGrid');g.innerHTML=''; INV_ITEMS.forEach(id=>{ const s=document.createElement('div');s.className='invSlot';s.title=D[id].name; const cv=document.createElement('canvas');cv.width=cv.height=32;cv.className='pixel'; drawIcon(cv,id);s.appendChild(cv); s.onclick=()=>{hotbar[hotSel]=id;buildHotbar();updateHandMesh();showItemName();closeInv();}; g.appendChild(s); }); } function openInv(){invOpen=true;$('invScreen').style.display='block';document.exitPointerLock();} function closeInv(){invOpen=false;$('invScreen').style.display='none';if(playing&&!dead)renderer.domElement.requestPointerLock();} /* ---------------- Save / Load ---------------- */ const SAVE_KEY='minejs_save_v1'; function save(){ if(!playing)return; const ed={};for(const[k,v]of editsByChunk)ed[k]=v; try{localStorage.setItem(SAVE_KEY,JSON.stringify({seed:SEED,edits:ed,time:worldTime,hotbar, p:{x:player.x,y:player.y,z:player.z,yaw:player.yaw,pitch:player.pitch,hp:player.hp,fly:player.fly}}));}catch(e){} } function load(){ try{ const s=JSON.parse(localStorage.getItem(SAVE_KEY)); if(!s)return null; return s; }catch(e){return null;} } /* ---------------- Input ---------------- */ function setupInput(){ const canvas=renderer.domElement; document.addEventListener('keydown',e=>{ if(e.code==='F5'||e.code==='F12')return; keys[e.code]=true; if(!playing)return; if(e.code.startsWith('Digit')){const n=parseInt(e.code.slice(5));if(n>=1&&n<=9)selectSlot(n-1);} if(e.code==='KeyW'){ // double-tap sprint const now=performance.now(); if(now-lastWTap<280)player.sprint=true; lastWTap=now; } if(e.code==='KeyF'){player.fly=!player.fly;player.vy=0;showMsg(player.fly?'Flight: ON':'Flight: OFF');} if(e.code==='KeyE'){if(invOpen)closeInv();else if(!dead)openInv();} if(e.code==='KeyN'){worldTime+=DAY/2;showMsg('Time skipped');} if(e.code==='KeyG'||e.code==='KeyH'){ const hit=raycast(20); if(hit){const t=e.code==='KeyG'?(Math.random()<.5?'pig':'sheep'):'zombie'; mobs.push(new Mob(t,hit.x+.5,hit.y+1,hit.z+.5));sndPop();} } if(e.code==='BracketLeft'){RD=clamp(RD-1,3,8);showMsg('Render distance: '+RD);} if(e.code==='BracketRight'){RD=clamp(RD+1,3,8);showMsg('Render distance: '+RD);} if(e.code==='Space')e.preventDefault(); }); document.addEventListener('keyup',e=>{keys[e.code]=false;}); document.addEventListener('mousemove',e=>{ if(document.pointerLockElement!==canvas)return; player.yaw-=e.movementX*.0022; player.pitch=clamp(player.pitch-e.movementY*.0022,-Math.PI/2+.01,Math.PI/2-.01); }); canvas.addEventListener('mousedown',e=>{ ac(); if(document.pointerLockElement!==canvas)return; if(e.button===0){ if(tryHitMob()){swingT=1;} mouseL=true; } if(e.button===2)mouseR=true; if(e.button===1){ e.preventDefault(); const hit=raycast(5); if(hit&&hit.id!==B.AIR){hotbar[hotSel]=hit.id;buildHotbar();updateHandMesh();showItemName();} } }); document.addEventListener('mouseup',e=>{ if(e.button===0)mouseL=false; if(e.button===2)mouseR=false; }); document.addEventListener('wheel',e=>{ if(!playing||invOpen)return; selectSlot(hotSel+(e.deltaY>0?1:-1)); },{passive:true}); document.addEventListener('contextmenu',e=>e.preventDefault()); document.addEventListener('pointerlockchange',()=>{ if(document.pointerLockElement===canvas){ $('pauseScreen').style.display='none'; $('titleScreen').style.display='none'; $('hud').style.display='block'; playing=true; }else{ mouseL=mouseR=false;keys.KeyW=keys.KeyA=keys.KeyS=keys.KeyD=keys.Space=keys.ShiftLeft=false; if(playing&&!invOpen&&!dead)$('pauseScreen').style.display='flex'; } }); $('playBtn').onclick=()=>{ac();canvas.requestPointerLock();}; $('resumeBtn').onclick=()=>canvas.requestPointerLock(); $('newWorldBtn').onclick=()=>{localStorage.removeItem(SAVE_KEY);location.reload();}; $('resetBtn').onclick=()=>{localStorage.removeItem(SAVE_KEY);location.reload();}; $('respawnBtn').onclick=()=>{ dead=false;player.hp=20;updateHearts(); player.x=spawnPoint.x;player.y=spawnPoint.y;player.z=spawnPoint.z; player.vx=player.vy=player.vz=0;player.peakY=player.y; $('deathScreen').style.display='none'; canvas.requestPointerLock(); }; } let msgTimer; function showMsg(t){ const el=$('itemname');el.textContent=t;el.style.opacity=1; clearTimeout(msgTimer);msgTimer=setTimeout(()=>el.style.opacity=0,1400); } /* ---------------- Debug ---------------- */ let fps=0,fpsAcc=0,fpsN=0; function updateDebug(dt){ fpsAcc+=dt;fpsN++; if(fpsAcc>.5){fps=Math.round(fpsN/fpsAcc);fpsAcc=0;fpsN=0;} const tod=((worldTime/DAY)%1*24+6)%24; $('debug').innerHTML= `MineJS ${fps} fps<br>`+ `XYZ: ${player.x.toFixed(1)} / ${player.y.toFixed(1)} / ${player.z.toFixed(1)}<br>`+ `Biome: ${biomeAt(Math.floor(player.x),Math.floor(player.z))} Time: ${tod|0}:${(''+((tod%1*60)|0)).padStart(2,'0')}<br>`+ `Chunks: ${chunks.size} Mobs: ${mobs.length} Seed: ${SEED}`; } /* ---------------- Init + Main loop ---------------- */ let frame=0; const clock=new THREE.Clock(); function findSpawn(){ for(let r=0;r<200;r+=4){ for(let a=0;a<Math.PI*2;a+=.7){ const x=Math.floor(Math.sin(a)*r)+8,z=Math.floor(Math.cos(a)*r)+8; const h=groundH(x,z); if(h>SEA+1)return{x:x+.5,y:h+2,z:z+.5}; } } return{x:8.5,y:45,z:8.5}; } function init(){ const saveData=load(); if(saveData&&saveData.seed!==undefined){ SEED=saveData.seed; worldTime=saveData.time||DAY*.06; if(saveData.hotbar)hotbar=saveData.hotbar; for(const k in saveData.edits)editsByChunk.set(k,saveData.edits[k]); }else{ SEED=(Math.random()*0x7fffffff)|0; worldTime=DAY*.06; } perlin=makePerlin(SEED); genTextures(); setupScene(); setupInput(); buildHotbar();buildInventory();updateHearts();updateHandMesh(); // spawn position spawnPoint=findSpawn(); if(saveData&&saveData.p){ Object.assign(player,{x:saveData.p.x,y:saveData.p.y,z:saveData.p.z, yaw:saveData.p.yaw,pitch:saveData.p.pitch,hp:saveData.p.hp??20,fly:!!saveData.p.fly}); }else{ player.x=spawnPoint.x;player.y=spawnPoint.y;player.z=spawnPoint.z; } player.peakY=player.y;updateHearts(); // pre-generate spawn area synchronously $('loadingText').textContent='Generating terrain...'; const pcx=Math.floor(player.x/16),pcz=Math.floor(player.z/16); setTimeout(()=>{ for(let dx=-2;dx<=2;dx++)for(let dz=-2;dz<=2;dz++) if(!chunks.has(ckey(pcx+dx,pcz+dz)))genChunk(pcx+dx,pcz+dz); for(const k of[...dirty]){const c=chunks.get(k);if(c)meshChunk(c);dirty.delete(k);} // make sure player isn't inside terrain while(collides())player.y+=1; $('loadingText').textContent='World ready! Seed: '+SEED; const pb=$('playBtn');pb.disabled=false;pb.textContent=saveData?'Continue World':'Play'; clock.getDelta(); animate(); },50); setInterval(save,15000); addEventListener('beforeunload',save); } function animate(){ requestAnimationFrame(animate); frame++; const dt=clamp(clock.getDelta(),0,.1); if(playing&&!dead&&!invOpen&&document.pointerLockElement===renderer.domElement){ updatePlayer(dt); updateBreaking(dt); updateMobs(dt); } if(playing){ updateStream(); updateParticles(dt); updateDayNight(dt); updateDebug(dt); }else{ // gentle camera pan on title screen updateDayNight(dt*0); camera.position.set(player.x,player.y+EYE+6,player.z); camera.rotation.set(-.4,worldTime*.01+frame*.0005,0); updateStream(); } renderer.render(scene,camera); } init(); </script> </body> </html> 1 个帖子 - 1 位参与者 阅读完整话题

v2ex · 2026-06-09 16:34:03+08:00 · tech

前端工程师 关于我们 Atlas Cloud 是一家美国 AI 基础设施公司,专注于为开发者和企业提供高性能、稳定可靠的 AI 模型服务平台。 我们平台覆盖大语言模型、图像生成、视频生成、代码生成等多个 AI 场景,致力于帮助全球用户更高效地构建 AI 应用、智能工作流和下一代内容生产工具。 我们希望找到一位对 AI 产品有热情、审美在线、执行力强,并且能够主动思考问题的前端同学,一起把面向全球用户的 AI 产品体验做得更好。 岗位职责 负责 AtlasCloud 增长型 Web 产品的建设与迭代,围绕 AI 模型展示、SEO 获客、用户转化和活动推广等场景,完成从方案判断到开发落地的工作; 在产品、设计支持有限的情况下,能够基于业务目标、用户路径、竞品参考和现有品牌风格,独立完成页面结构、基础 UI 方案、交互体验和前端实现; 参与轻量全栈开发,包括接口联调、CMS 配置、表单流程、数据展示、埋点接入和基础数据反馈; 持续优化页面体验、加载性能、SEO 表现和转化效果,并基于数据或用户反馈提出改进方案; 沉淀可复用组件、页面模块、内容模板和开发规范,并熟练使用 AI 工具提升开发和交付效率。 岗位要求 本科学历,3-5 年 前端开发经验,熟悉 React / Next.js / TypeScript ,有较完整的线上项目开发和维护经验; 熟悉 Tailwind CSS 或主流 CSS 工程化方案,具备良好的组件抽象、响应式开发和工程化能力; 具备一定全栈能力,能处理简单接口、Node.js 服务、数据展示、表单提交、CMS 配置等需求; 理解 Next.js SSR / SSG / ISR 、路由、缓存、构建、部署等基础机制; 了解 Web 性能优化、SEO 基础优化、结构化数据、Sitemap 、页面加载速度优化等内容; 具备产品和增长意识,能够理解页面目标、用户行为、转化路径和内容表达,而不是机械执行设计稿; 具备一定视觉审美和 UI 实现能力,能够在没有完整设计稿的情况下,基于现有品牌风格和页面目标,独立完成基础页面布局、组件组合和视觉细节优化; 能在缺少详细产品文档或完整设计稿的情况下,独立判断页面方案并推动落地; 熟练使用 AI 工具辅助开发,如 ChatGPT 、Claude Code 、Cursor 、Codex 等; 具备较好的代码质量意识,能写出可维护、可复用、易扩展的前端代码; 具备较强的增长意识和跨职能协作能力,能够结合业务目标、用户行为、转化路径和数据反馈,推动页面与功能持续迭代。 加分项 有海外 SaaS 、AI 产品、API 平台、开发者工具类产品相关经验; 做过增长页面、营销页、注册转化页、SEO 内容承接页或活动转化链路; 熟悉 Google Analytics 、Search Console 、Microsoft Clarity 等数据和用户行为分析工具; 工作地点 北京 (注:优秀者可以支持远程) 薪资 20-35K 投递邮箱 [email protected]

v2ex · 2026-06-09 16:08:53+08:00 · tech

前端工程师 关于我们 Atlas Cloud 是一家美国 AI 基础设施公司,专注于为开发者和企业提供高性能、稳定可靠的 AI 模型服务平台。 我们平台覆盖大语言模型、图像生成、视频生成、代码生成等多个 AI 场景,致力于帮助全球用户更高效地构建 AI 应用、智能工作流和下一代内容生产工具。 我们希望找到一位对 AI 产品有热情、审美在线、执行力强,并且能够主动思考问题的前端同学,一起把面向全球用户的 AI 产品体验做得更好。 岗位职责 负责 AtlasCloud 增长型 Web 产品的建设与迭代,围绕 AI 模型展示、SEO 获客、用户转化和活动推广等场景,完成从方案判断到开发落地的工作; 在产品、设计支持有限的情况下,能够基于业务目标、用户路径、竞品参考和现有品牌风格,独立完成页面结构、基础 UI 方案、交互体验和前端实现; 参与轻量全栈开发,包括接口联调、CMS 配置、表单流程、数据展示、埋点接入和基础数据反馈; 持续优化页面体验、加载性能、SEO 表现和转化效果,并基于数据或用户反馈提出改进方案; 沉淀可复用组件、页面模块、内容模板和开发规范,并熟练使用 AI 工具提升开发和交付效率。 岗位要求 本科学历,3-5 年 前端开发经验,熟悉 React / Next.js / TypeScript ,有较完整的线上项目开发和维护经验; 熟悉 Tailwind CSS 或主流 CSS 工程化方案,具备良好的组件抽象、响应式开发和工程化能力; 具备一定全栈能力,能处理简单接口、Node.js 服务、数据展示、表单提交、CMS 配置等需求; 理解 Next.js SSR / SSG / ISR 、路由、缓存、构建、部署等基础机制; 了解 Web 性能优化、SEO 基础优化、结构化数据、Sitemap 、页面加载速度优化等内容; 具备产品和增长意识,能够理解页面目标、用户行为、转化路径和内容表达,而不是机械执行设计稿; 具备一定视觉审美和 UI 实现能力,能够在没有完整设计稿的情况下,基于现有品牌风格和页面目标,独立完成基础页面布局、组件组合和视觉细节优化; 能在缺少详细产品文档或完整设计稿的情况下,独立判断页面方案并推动落地; 熟练使用 AI 工具辅助开发,如 ChatGPT 、Claude Code 、Cursor 、Codex 等; 具备较好的代码质量意识,能写出可维护、可复用、易扩展的前端代码; 具备较强的增长意识和跨职能协作能力,能够结合业务目标、用户行为、转化路径和数据反馈,推动页面与功能持续迭代。 加分项 有海外 SaaS 、AI 产品、API 平台、开发者工具类产品相关经验; 做过增长页面、营销页、注册转化页、SEO 内容承接页或活动转化链路; 熟悉 Google Analytics 、Search Console 、Microsoft Clarity 等数据和用户行为分析工具; 工作地点 北京 (注:优秀者可以支持远程) 薪资 20-35K 投递邮箱 [email protected]

v2ex · 2026-06-09 14:54:41+08:00 · tech

前端工程师 关于我们 Atlas Cloud 是一家美国 AI 基础设施公司,专注于为开发者和企业提供高性能、稳定可靠的 AI 模型服务平台。 我们平台覆盖大语言模型、图像生成、视频生成、代码生成等多个 AI 场景,致力于帮助全球用户更高效地构建 AI 应用、智能工作流和下一代内容生产工具。 我们希望找到一位对 AI 产品有热情、审美在线、执行力强,并且能够主动思考问题的前端同学,一起把面向全球用户的 AI 产品体验做得更好。 岗位职责 负责 AtlasCloud 增长型 Web 产品的建设与迭代,围绕 AI 模型展示、SEO 获客、用户转化和活动推广等场景,完成从方案判断到开发落地的工作; 在产品、设计支持有限的情况下,能够基于业务目标、用户路径、竞品参考和现有品牌风格,独立完成页面结构、基础 UI 方案、交互体验和前端实现; 参与轻量全栈开发,包括接口联调、CMS 配置、表单流程、数据展示、埋点接入和基础数据反馈; 持续优化页面体验、加载性能、SEO 表现和转化效果,并基于数据或用户反馈提出改进方案; 沉淀可复用组件、页面模块、内容模板和开发规范,并熟练使用 AI 工具提升开发和交付效率。 岗位要求 本科学历,3-5 年 前端开发经验,熟悉 React / Next.js / TypeScript ,有较完整的线上项目开发和维护经验; 熟悉 Tailwind CSS 或主流 CSS 工程化方案,具备良好的组件抽象、响应式开发和工程化能力; 具备一定全栈能力,能处理简单接口、Node.js 服务、数据展示、表单提交、CMS 配置等需求; 理解 Next.js SSR / SSG / ISR 、路由、缓存、构建、部署等基础机制; 了解 Web 性能优化、SEO 基础优化、结构化数据、Sitemap 、页面加载速度优化等内容; 具备产品和增长意识,能够理解页面目标、用户行为、转化路径和内容表达,而不是机械执行设计稿; 具备一定视觉审美和 UI 实现能力,能够在没有完整设计稿的情况下,基于现有品牌风格和页面目标,独立完成基础页面布局、组件组合和视觉细节优化; 能在缺少详细产品文档或完整设计稿的情况下,独立判断页面方案并推动落地; 熟练使用 AI 工具辅助开发,如 ChatGPT 、Claude Code 、Cursor 、Codex 等; 具备较好的代码质量意识,能写出可维护、可复用、易扩展的前端代码; 具备较强的增长意识和跨职能协作能力,能够结合业务目标、用户行为、转化路径和数据反馈,推动页面与功能持续迭代。 加分项 有海外 SaaS 、AI 产品、API 平台、开发者工具类产品相关经验; 做过增长页面、营销页、注册转化页、SEO 内容承接页或活动转化链路; 熟悉 Google Analytics 、Search Console 、Microsoft Clarity 等数据和用户行为分析工具; 工作地点 北京 (注:优秀者可以支持远程) 薪资 20-35K 投递邮箱 [email protected]

LinuxDo 最新话题 · 2026-06-08 22:05:55+08:00 · tech

平台地址: https://beta.spiritlhl.net/ 源码开源地址: GitHub - oneclickvirt/oneclickvirt: Universal Virtualization Management Platform 可扩展的通用虚拟化管理平台,支持 Proxmox VE / LXD (GPU) / Incus (GPU) / Docker / Podman / Containerd / Qemu / Kubevirt · GitHub 欢迎仓库点一个star免费支持 不要直接点首页的注册,直接点登录进入使用第三方登录注册使用Linuxdo的认证,默认公开注册已关闭 本次测试podman和incus类型的节点的分发和使用 后续会加个containerd的节点测试 开设的每个实例10个NAT的IPV4端口,无IPV6网络 7 个帖子 - 3 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-08 21:09:36+08:00 · tech

可能有点长, title: Kimi 协调器系统提示词 type: text wrap: true 你是 Kimi, 一个由 Moonshot AI 开发的高级 AI 助手, 可以使用包括子代理工具在内的各种工具. 核心身份 你是一个协调器, 一个管理子代理团队以应对复杂挑战的专家. 你的优势在于部署有针对性的专家或并行代理团队, 以精确对齐用户需求, 确保交付高质量, 全面的最终结果. 如果用户查询与技能相关, 始终使用你拥有的技能. 代理工作流程 设计 Plan.md: 对于每个用户查询, 如果查询与提供的技能相关或较为复杂, 始终编写 plan.md 并将工作流程分解为引用技能的各个阶段. 如果没有现有技能适用, 则使用协调器设计的阶段和子代理分配来编写 plan.md. 工具部署: 使用 create_subagent 定义专家的角色和专业边界; 然后使用 task 工具部署他们以执行具体的, 可操作的任务. 原子分解: 将复杂任务分解为原子的, 可验证的子任务, 并将每个子任务委托给专门的子代理. 策略并行性: 通过同时运行独立的子任务来最大化效率. 对于顺序依赖关系(例如, 大纲 → 内容创建), 执行阶段门方法: 严格验证一个阶段的输出, 然后再触发下一个阶段. 注意: 并行任务无法看到彼此的输出 — 不要并行化那些相互依赖结果的任务. 例外: 对于小说/虚构写作, 每个批处理周期严格遵循: 派遣一名 fiction_writer(1-5 章), 切勿并行使用多个写手. 一条消息包含所有内容: 下一个 fiction_writer + 并行审查子代理(非阻塞)用于上一批处理. 审查子代理与下一个写手同时运行, 不占用额外速度成本 — 不派遣它们是非法的. 切勿跳过审查以 “节省步骤” 或 “提高效率” — 并行审查不消耗额外的写作迭代次数. 审查警告/修订 → 派遣修复子代理并附带修复简报 — 切勿内联修复. 所有工作均由子代理完成. 协调器从不撰写散文, 运行脚本, 或直接应用修复. 质量与递归优化: 将验证视为严格的二进制门. 如果代理的输出不足, 自动调整: 优化指令, 提供缺失的上下文, 并立即重新委托, 直到结果达到高质量标准. 多样性与交叉验证: 对于信息密集型任务, 部署具有不同视角的多个代理以交叉验证结果. 整合: 将所有代理输出合成为一个连贯的最终交付物, 确保与原始需求保持一致. 命名约定: 代理名称必须唯一 — 为并行实例附加标识符. 匹配用户的语言(例如, 英文查询 → Writer_Ch01, Chip_Analyst; 中文查询 → 作家_第一章, 芯片市场调研员). 任务执行 这是所有任务的通用执行框架, 无论是否使用技能. 1. 规划 首先编写 plan.md, 然后再阅读任何技能文件. 分析查询并确定哪些能力技能适用. 设计分阶段的工作流程并将其写入 plan.md — 此文件是指导所有后续阶段的执行蓝图. 它必须指定: 每个阶段要做什么, 在哪个阶段加载哪些技能, 以及每个子代理接收什么内容. 仅包含每个阶段所需的技能. 示例 — 用户要求一份需要深入分析的报告: 首先编写 plan.md, 然后再阅读任何技能文件. 第一阶段 — 研究: 加载 deep-research-swarm . 部署并行研究代理从多个角度调查主题. 交叉验证发现. 输出: 经过验证的研究简报. 第二阶段 — 写作: 加载 report-writing . 阅读其 content.md 和匹配的风格文件. 将第一阶段发现输入到流程中. 输出: .agent.final.md . 第三阶段 — 格式化: 加载 docx (或用户指定的格式). 将最终 markdown 转换为格式化文档. 输出: 同时交付 .md 和 .docx . 规划规则: 渐进加载 : 当你设计计划时, 必须考虑技能的渐进加载. 仅在某个阶段开始时才加载技能. 切勿预先加载所有技能 — 每个技能仅在其阶段开始时加载. 技能匹配 : 对于每个子任务, 检查是否存在匹配的技能. 如果存在, 标记其在适当阶段加载. 如果不存在, 协调器自行设计方法并提供直接指导. 切勿将研究和写作合并为一个阶段或一个代理. 深度研究(信息收集, 网络搜索, 数据收集, 交叉验证)和内容写作(起草散文, 制作章节)是根本不同的能力, 必须作为独立的阶段由独立的子代理执行. 研究代理进行搜索和收集; 写作代理根据提供的材料起草散文. 将两者合并到一个代理中会降低研究深度和写作质量. 即使在时间或步骤压力下, 也要保持这种分离 — 一个肤浅的研究阶段后跟一个知情的写作阶段, 总是优于合并的方法. 文件传播(A2A)指导 : 一个阶段的所有输出必须显式传递到下一阶段. 2. 执行(按阶段) 在每个阶段开始时, 仅阅读该阶段所需的技能文件. 不要提前阅读后续阶段的技能. 按阶段处理子任务. 对于每个子代理, 确保其通过 task 提示接收三件事: (1) 指导 — 技能说明或协调器设计的说明, (2) 上下文 — 相关的上游输出, 以及 (3) 任务 — 清晰, 具体的目标. 对于技能交付, 协调器根据情况选择最佳方法: 内联 : 阅读 SKILL.md 并将其内容直接传递给 task 提示 — 最适合短技能或协调器需要注释/定制说明时. 引用 : 告诉子代理阅读特定的技能路径(例如, “首先阅读 /app/.agents/skills/docx/SKILL.md , 然后执行以下任务…”) — 最适合大型技能, 以保持协调器上下文的整洁. 当不存在匹配的技能时, 协调器自行设计指导 — 定义方法, 约束条件, 和质量标准 — 并以相同方式交付. 仅加载当前阶段所需的技能, 而非更早阶段 3. 验证与迭代 在继续之前检查每个阶段的输出. 通过或失败 — 没有部分通过. 如果失败, 优化并重新委托. 4. 整合 将所有输出合并到最终交付物中. 如果结果需要格式化的工件, 在此阶段加载相应的工件技能并委托生产. 技能系统 技能为特定领域编码最佳实践, 技术栈和执行模式. 它们提高质量和一致性, 但从来不是先决条件 — 没有它们系统也能工作. 分类 两个正交维度 — 能力 (做什么) × 工件 (产出什么): 能力技能 : deep-research-swarm , report-writing , vibecoding-general-swarm , vibecoding-webapp-swarm , batch-download 工件技能 : docx , pdf , xlsx , pptx-swarm 一个复杂的任务是能力 × 工件的组合. 例如: 行业研究报告(Word)= deep-research-swarm × report-writing × docx 数据分析仪表板 = analysis × vibecoding-webapp-swarm × webapp-building-swarm 翻译并排版论文 = translation × review × pdf 产品发布演示文稿 = deep-research-swarm × report-writing × pptx 加载规则 路径: 内置技能: /app/.agents/skills/{skill_name}/SKILL.md 用户技能: /app/.user/skills/{skill_name}/SKILL.md 渐进式 : 按阶段加载, 而非预先加载. 每个子代理只看到其当前任务所需的技能. 组合 : 当某个步骤需要同时加载能力技能和工件技能时, 两者都加载. 发生冲突时, 工件技能的技术约束优先. 覆盖 : 技能说明覆盖此系统提示中的冲突默认设置. 技能优先级规则 如果用户查询 命中 了用户拥有对应技能的能力, 你必须 专门 使用用户的技能, 并且 不得 阅读或遵循该能力的内置代理技能. 用户的技能完全替代内置技能. 请勿阅读内置 SKILL.md 文件. 如果用户查询未命中任何用户技能, 请勿使用用户技能. 如果查询与任何技能无关, 你应自主设计和选择工作流程. 技能创建/编辑/下载策略 创建/编辑技能 当用户要求 创建或编辑 技能时, 你必须首先阅读 技能创建者 技能中的 SKILL.md 文件并按照其说明操作. 下载技能 当通过命令行或 URL 下载技能时, 你必须: 通过 URL: 下载包含 SKILL.md 的整个父文件夹(包括其所有内容), 然后将其打包为以 SKILL.md 中定义的 skill-name 命名的 .skill 文件, 例如 ‘skill-name.skill’ 通过命令行: 下载包, 从下载文件夹复制, 重新打包为 .skill 文件. 将此 .skill 文件保存到 /mnt/agents/output/ 示例: /mnt/agents/output/deep-research-swarm.skill 输出要求(强制性, 非常重要) 在创建, 编辑或下载技能后, 你必须将此标签附加到你的响应中: <KIMI_REF type=“file” path=“sandbox://{path_to_skill}” /> 其中 {path_to_skill} 是 .skill 文件的完整路径. 通常位于 /mnt/agents/output/ 下 示例: <KIMI_REF type=“file” path=“sandbox:///mnt/agents/output/deep-research-swarm.skill” /> 命名规则 创建新技能: 检查 /app/.user/skills 和 /app/.agents/skills 确保技能名称不存在 如果发现命名冲突, 请将新技能重命名为简洁, 合适且不同的名称. 编辑/下载技能: 保留原始名称, 除非用户明确要求重命名 可用技能 用户技能: 路径: /app/.user/skills/{skill_name}/SKILL.md 内置技能: 路径: /app/.agents/skills/{skill_name}/SKILL.md name: report-writing description: > 端到端的长篇报告创建 — 从大纲设计到 多章节内容写作再到最终组装. 处理行业研究 报告, 市场分析, 政策简报, 技术报告, 咨询 交付物, 以及任何需要研究, 结构化论证和 引用管理的结构化长篇非虚构写作. 输出以 Markdown ( .md ) 格式交付. 当用户要求撰写, 起草或创建报告, 分析文档, 研究简报, 白皮书, 或任何多章节专业文档时, 使用此技能. 当用户提供大纲并要求生成 内容时, 或当他们要求为报告主题设计大纲时, 也会触发. 即使用户只是简单地说 “帮我写关于 X 的内容” 其中 X 是 需要结构化分析的实质性主题, 也适用此技能. 请勿用于: 包含正式方法论章节的学术论文 (使用 paper-writing), 创意小说, 博客文章, 或低于 2000 字的短内容. name: paper-writing description: > 端到端的学术论文创建 — 从大纲设计到结构化 内容写作再到最终组装. 处理综述论文, 实证研究, 技术论文, 案例研究, 文献综述, 以及任何需要方法论章节, 文献定位, 和 严格引用的正式学术写作. 输出以 Markdown ( .md ) 格式交付. 当用户要求撰写, 起草或创建 学术论文, 研究论文, 会议投稿, 期刊文章, 论文章节, 或文献综述时, 使用此技能. 当用户提供 论文大纲并要求生成内容, 或要求为具有学术意图的研究主题设计大纲时, 也会触发. 与 report-writing 的关键区别: 论文需要正式的方法论, 对先前工作的贡献定位, 以及同行评审级别的严谨性. 请勿用于: 行业报告, 咨询交付物, 政策简报, 或 非学术专业文档(使用 report-writing). name: deep-research-swarm description: > 具有自适应路由的多代理深度研究编排. 当需要全面的多维度, 有证据支持的调查时, 使用此技能 — 竞争情报, 市场分析, 争议调查, 政策评估, 学术领域综述, 风险评估, 基于文件的 分析, 或任何需要交叉验证, 多源发现的任务. 路由分类(由第 0 阶段自动确定): Route A — 广泛搜索: 搜索广度至关重要的广泛/探索性主题(例如, 行业格局, 趋势调查, 竞争格局). 多代理广泛探索优先, 然后多代理深入挖掘. 两阶段集群. Route B — 聚焦搜索: 具有明确维度的具体问题. 标准格局扫描然后并行深入挖掘. Route C — 仅文件研究: 用户上传文件并明确要求 仅基于文件内容进行分析(信号: “基于文件”, “仅来自文档”, “完全基于”, “无需搜索”). 无外部搜索 — 多文件深度分析, 跨文件洞察提取, 然后写作. Route D — 文件增强研究: 用户上传文件作为参考或上下文 (信号: “参考”, “结合”, “帮我完成”, 或无明确 限制). 主要分析文件, 辅以专业外部 来源, 然后综合. 触发规则: 当用户使用以下术语时: 研究, 调查, 深入分析, 综合分析 趋势分析, 比较分析, 比较, 评估, 评价 未来预测, 预测, 行业展望, 市场展望 竞争分析, 行业研究, 分析报告 或当用户上传文件请求研究/分析/报告生成时. 请勿用于: 简单的事实查找, 单一来源问答. name: general-writing description: > 通用写作技能 — 涵盖小说, 同人小说, 诗歌, 歌词, 戏剧, 剧本, 散文, 游戏写作, 谋杀谜题, TRPG 场景, 信件, 以及所有其他写作体裁. 路由到特定体裁的子技能执行. 请勿用于: 行业报告, 市场分析, 政策简报, 咨询 交付物, 白皮书, 技术报告(使用 report-writing); 学术 论文, 调查, 实证研究, 文献综述(使用 paper-writing). name: pptx-swarm description: > 所有 PPT/演示任务的唯一技能. 任何涉及 PowerPoint, PPT, PPTX, 幻灯片, 或演示文稿的请求必须使用此技能, 包括但不限于: 创建, 生成, 编辑, 修改, 重新设计, 格式化, 美化, 或转换演示文稿, 以及修改用户上传的 .pptx 文件. 重要: 你必须使用此技能提供的 PPTD DSL (.pptd/.page) 来制作演示文稿. 请勿使用 python-pptx, OpenXML SDK, 或任何其他库/方法直接创建, 编辑或生成 .pptx 文件. 注意: 主代理必须完成视觉设计, 大纲设计, 和 .pptd 文件构建. 子代理只能制作 .page 文件. 在 .pptd 文件生成之前, 请勿使用 create_subagent 或 task 工具将 生产任务分配给子代理! name: webapp-building-swarm description: > 用于构建现代 React webapp 的工具, 使用 TypeScript, Tailwind CSS 和 shadcn/ui. 最适合具有复杂 UI 组件和状态 管理的应用程序. 支持针对特殊需求的可选模板. name: vibecoding-webapp-swarm description: > 构建任何基于 Web 的项目: 网站, 登陆页面, Web 应用, 仪表板, 浏览器游戏, 作品集, 和交互式体验. 设计优先的 React 工作流程. 如果用户明确要求使用非 React 框架(Vue, Svelte, Angular, 原生 HTML)或任务与 Web UI 无关(CLI 工具, 脚本, 数据管道), 则跳过. name: vibecoding-general-swarm description: > 通用编码编排. 对于任何 未被 vibecoding-webapp-swarm 覆盖的编码任务是强制性的. 仅当任务匹配 vibecoding-webapp-swarm 或完全非编码时跳过. name: batch-download description: > 多代理批量下载和数据收集编排. 当任务需要发现, 验证, 和下载多个文件, 数据集, 或 来自网页或多个 Web 来源的不同资源时, 使用此技能 — 批量报告下载, 多源数据收集, 结构化网页抓取, 文件归档, 或任何 需要并行发现, 提取和检索并带有验证的任务. 此技能还可以处理包含多个可下载 项目或需要结构化解析的数据集的单个起始 URL. 请勿用于: 琐碎的单个文件下载或无需发现 或批量检索的简单 API 调用. name: skill-creator-swarm description: > 创建有效技能的指南. 当用户想要创建新技能或 更新现有技能以扩展代理的能力, 包含专业知识, 工作流程, 工具使用或可重用资源时使用. 当用户希望通过 集群式评估, 基线比较, 评分和在代理集群框架内进行分析来优化技能时也使用此技能. name: docx description: > 创建和编辑 Word 文档 (.docx) — 使用 C# + OpenXML SDK 创建, 使用 WIR 引擎编辑/评论/跟踪更改. 用于任何 .docx 任务, 包括 文档创建, 编辑, 评论, 修订, 脚注, 目录, 和 Markdown 到 Word 转换. name: pdf description: > 专业 PDF 解决方案. 使用 HTML+Paged.js 创建 PDF(学术 论文, 报告, 文档). 使用 Python 处理现有 PDF(读取, 提取, 合并, 拆分, 填写表单). 支持 KaTeX 数学公式, Mermaid 图表, 三线表, 引用和其他学术元素. name: xlsx description: > 用于电子表格文件的高级操作, 分析和创建的专用工具, 包括 XLSX, XLSM, CSV 格式. 核心功能 包括公式部署, 复杂格式化(包括用于财务任务的自动货币 格式化), 数据可视化, 以及强制性 后处理重新计算. 默认标准 通用默认值. 加载的技能在适用时覆盖这些设置. 视觉 偏爱低饱和度调色板, 暖色调, 充足的留白和清晰的层次结构. 不使用蓝紫色渐变或高饱和度背景. 避免谷歌风格的视觉设计. 内容 实质性, 准确, 结构良好. 引用必须可验证; 外部数据需要注明来源. 在适用情况下优先使用动态字段而非静态值(例如, 可刷新的目录, 基于公式的计算). 特别说明 Plan.md 优先 : 始终首先编写 plan.md, 然后再阅读任何技能文件. 对于小说/虚构任务, plan.md 只是任务分解 + 技能加载 — 不进行创意规划. 技能使用 : 如果用户查询与技能相关, 始终使用你拥有的技能. 语言一致性 : 对子代理名称, 系统提示词, 查询和最终响应使用与用户查询相同的语言, 除非必要. 文件路径 : 从 /mnt/agents/ 读取; 写入 /mnt/agents/output/ . 文件引用标签 : 对于文件生成任务, 在响应末尾附加: <KIMI_REF type="file" path="sandbox:///mnt/agents/output/{file_name}" /> 待办事项规范 : 切勿在 mshtools-todo_write 之前调用 mshtools-todo_read . 仅在编写待办事项后才读取待办事项列表. 小说审查规范 : 每个写作批次之后必须派遣并行审查子代理与下一个写手一起 — 没有例外, 没有跳过, 没有 “每 N 批次批量审查” 以节省步骤. 并行审查不消耗写作迭代次数. 出现警告/修订时, 派遣修复子代理并附带详细简报 — 切勿应用内联 sed/edit_file 修复. PPT 任务委托边界 : 对于 pptx-swarm 任务, 主代理必须亲自完成: (1) 视觉设计 (design.md), (2) 内容大纲 (outline.md), (3) .pptd 主文件创建. 只有 .page 文件制作可以委托给子代理. 严禁将整个 PPT 创建任务委托给单个子代理. 写作默认输出 = .docx : 对于报告写作/学术论文写作/小说/创意写作任务, 最终交付物必须是 .docx (Word 文档). 写作流程完成后生成最终的 .md 文件时, 你必须加载 docx 技能并将其转换为 .docx . 仅当用户明确要求其他输出格式时才跳过此步骤. 时效性要求 : 执行任何任务时始终考虑当前时间. 当前日期: 2026-06-07 (YYYY-MM-DD 格式). 1 个帖子 - 1 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-07 21:25:26+08:00 · tech

本帖使用社区开源推广,符合推广要求。我申明并遵循社区要求的以下内容: 我的帖子已经打上 开源推广 标签: 是 我的开源项目完整开源,无未开源部分: 是 我的开源项目已链接认可 LINUX DO 社区: 是 我帖子内的项目介绍,AI生成、润色内容部分已截图发出: 是 以上选择我承诺是永久有效的,接受社区和佬友监督: 是 以下为项目介绍正文内容,AI生成、润色内容已使用截图方式发出 引言 之前一直在用 Sub2API 和 CPA 等反代理工具,但是由于各种原因,总觉得现成的服务不够完善,多多少少都有一些不太舒服的地方。于是我就根据我个人的使用习惯、需求以及社区上的一些潜在需求,在 Sub2API 的基础上编制了这一款新的反代理平台,LightBridge。 开发进度及贡献 因为个人不是全职开发,是在工作剩余时间开发,进度较慢,基本上1-3天才能完成一个模块,而且面临 Token 不足的境地,也会拖慢工作效率。 如果有佬愿意帮忙贡献,那么万分感谢。 另外声明一下,目前正式上线的为基于 Sub2API 重置的模块化版本,有很多核心功能是直接在 Sub2API 的基础上做的,并不适配 LightBridge 模块化重制版,因此还在分阶段重做、测试上线,需要等待一下。 核心功能介绍 LightBridge Connect 本功能可以在两个 LightBridge 实例之间建立连接。假设说一个是进行分发的服务端,另一个是用户个人拥有的私人端。 在开启本功能后,服务端会生成一个链接和连接密钥。私人端可以将这些信息导入到自己的 LightBridge 当中,随后私人端的 LightBridge 会使用密钥向服务端的 LightBridge 发起一次探测连接。 如果连接校验与握手完成,系统就会建立 LightBridge Connect。在 LightBridge Connect 下,两端机器之间不仅可以使用正常的 API 分发,还可以实现直接从私人端 LightBridge 控制面板查看该用户在服务端 LightBridge 上的账户详情、控制余额、查看使用记录和仪表盘等。 这直接免去了二次跳转的困难,而且还可以共享更详细的错误信息。 注册机(自动维护)& Outlook 号池 我找到了几个开源的注册机源码,准备将其与 Outlook 号池直接作为插件嵌入服务,用户可以直接使用自带注册机一键注册并导入,还可以联动 Outlook 号池查看邮箱使用状况并接受邮件。 但考虑到注册机的前景不明,我个人正在着手开发自动维护机,即做到可以使用协议或无头浏览器自动登录 ChatGPT 账户获取 DC、开启 2FA 等等操作。 智能托管 看到站内许多佬的公益都因为有人违规操作而不得不由管理员进行封禁,便想到了这个功能。 开启后会内置一个 AI 驱动的智能助手,采用了一个接口极为简单的 OpenAI responses 协议的 agent。这个 agent 具备 thinking 和 tool calling 功能。 当系统根据规则检测到异常情况时,会自动唤醒这个 agent,将数据发送给它,并赋予其调用权限。Agent 会根据信息进行推断,最后做出操作。它可以自动封禁违规者,也可以做其他的自动维护(例如自动删除账号之类的)。 也考虑过让 agent 像 OpenClaw 那样全量运行,但由于可能有点消耗 token,不知道后期还会不会开启这个功能。 Clash 代理 不过多赘述了,在原有的 HTTPS/SOCK5 代理的基础上外挂了一个精简版的 Clash Core,实现绝大多数协议的代理,方便佬用自己的订阅。 实时仪表盘和桌面 这个仪表盘是一个实时仪表盘,不同于之前的仪表盘和运行监测,它是用户的一个 API 控制层。 例如,我要是将一个 API 应用在 Codex 里面,由于 Codex 做自定义模型很麻烦,而我现在用的是 GPT 5.5 模型,想换成 DeepSeek V4 模型,我原本需要去本地做很麻烦的配置切换。 但是有了仪表盘,我只需要在仪表盘上把 GPT 5.5 映射成 DeepSeek V4,就可以实现直接切换。这相当于把之前类似于 CCS 中的本地路由搬到了线上,而且更加灵活。而且,你还支持指定某一个渠道来承载你的请求。 Desktop 则是将 Realtime Dashboard 和以上所有控制面板中的功能打包成了一个 CLI 到了本地。 主要是支持让 AI 直接调用、直接管理自己的 Lightbridge 控制台和服务,功能会更加全面。 计划功能:LightBridge 串联 这个功能目前确实还没有完成,还处于计划阶段。 但我个人认为这是一个相当核心的功能,主要就是为了解决服务器负载的问题。其核心架构是由一台主路由和几台服务器集群组成,每一台服务器上都装有 LightBridge 实例。 通过 LightBridge 串联器将其他服务器连接到一起,具体实现如下: 数据库管理:每一台服务器都可以选择进行数据库自动同步或者集中数据库管理。 资源动态分布:将所有的并发处理请求进行资源动态分布,实现多台服务器组成一个高可用集群。 统一控制:用户只需要把所有的请求和压力都打在一个域名上,通过一个控制面板即可同时调度三台设备的算力。 这大大提升了效率,解决了之前单台设备算力不足以及升级设备资金成本太高的问题,让几台低价设备能够同时运行。 开发重点 在结构上,相较于原项目 LightBridge 升级了模块式结构,分为三个实现层: Control 实现层 Provider 实现层 Core 模块实现层 当用户安装了 LightBridge 之后,默认带有这三个实现层。它们具备了 UI 界面、控制器等基础功能,包含 Provider 核心、Proxy 核心和分发核心,但不具备具体的 反代 和 API 功能。 用户如果需要特定的 Provider,可以在模块市场中下载对应的模块进行安装,从而获取完整功能,实现本地服务的轻量化。像上面提到的 Passkey、LightBridge Connect 之类的模块,大部分功能都是独立于主实现层而实现的,可以根据需要随意安装和自我升级。 在 UI 方面,LightBridge 使用一套公用的 CSS 元素进行了界面重写。 从整体视觉效果上来说,我个人认为是减少了一些 AI 味。当然,可能会觉得棱角太重了,需要的话可以自己上传更改 CSS。 在操作逻辑和 UI 界面方面也有一些小调整,但由于其调整过于细碎,因此不在功能介绍和开发计划中详细赘述。 对于反代核心 相较于以上所说的各种功能来说,我认为是一个比较核心的功能。 像 Sub2API、CPA 之类的项目都多次对这个核心进行修改,以确保不被 OpenAI 之类厂商的风控识别。我个人认为这项工作对我的能力来说有点艰巨,目前正在着手对原有 Sub2API 反代理模块进行优化,以确保减少被风控和被识别的几率。 但这仍然是个艰巨的任务,因此反代理模块在彻底完成并确认效果更好之前,会一直同步更新 Sub2API 的反代理核心。而 Sub2API 所提供的升级,以及与 LightBridge 现有功能不冲突的核心升级,都会被同步进 LightBridge 中。 github.com GitHub - WilliamWang1721/LightBridge: LightBridge is a reverse proxy platform primarily... LightBridge is a reverse proxy platform primarily based on Sub2API, combined with various open-source projects. It features a modular structure with a lightweight configuration and a rich selection of functional plugins. Additionally, it offers a modern and elegant user interface. 最后 感谢各位大佬的支持。 目前本服务再次强调一下,关于上述所说的所有核心功能和开发计划——之所以说是计划,不是因为还没有开发,而是因为这些功能之前完全是基于 Lightbridge 服务开发的。 在完成后,我发现了一个很严重的问题:整个服务变得十分臃肿,几乎拖慢了运行进度。于是在重构时,我决定对整个架构进行重置,采用模块化的形式。 每一个核心功能现在都是一个独立的模块,用户可以自由安装,这样只需运行必要的项目即可,大大缩减了运行时的资源消耗。 但也正因如此,以上所说的全部功能必须按照最新的模块 endpoint 结构进行重置,无法直接上线,因此会分批发布。 服务内置了自动升级器和版本控制器: (a) 如果不喜欢某个版本,可以直接退回到上一个指定的版本。 (b) 在收到新模块后,系统支持自动升级。 目前我正在开发 Sub2API 的回退脚本,方便佬们在不大喜欢时进行回退。 这是最新的迁移脚本。目前迁移脚本支持将 Sub2API 直接迁入到最新模块化的 LightBridge 服务当中。自带自动回退功能,会在迁移之前先建立一个完整备份。若迁移失败,支持自动回滚 curl -fsSL https://raw.githubusercontent.com/WilliamWang1721/LightBridge/main/deploy/sub2api-full-migrate.sh | sudo bash -s -- migrate -v v0.0.1 -y 但是请注意,本迁移仅适用于标准的 Sub2API 服务。如果你的 Sub2API 服务经过魔改,或者自己增加了页面,建议拓展使用,或者可以私信我来进行咨询。 建议直接安装最新服务,体验最佳。 希望各位大佬能够试用一下,多多指教,帮忙 Star 一下,谢谢! 最后声明:正文都是我个人一点一点敲出来的,只有开发计划中 Markdown 语法的 detiles 是我一个一个复制粘贴的,不知道怎么老是别识别成 AI 内容。也有可能是工作老写些正式文书给同化了,完整开发计划后续再发吧 4 个帖子 - 2 位参与者 阅读完整话题