返回资讯中心
外部精选
软件工程
#开发者实时流

Show HN:Id|le 让 AI 小生物住进浏览器,新增故事模式

来源标题:Show HN: Id|le, little AI-creatures living in the browser (story mode)

开发者发布浏览器项目 Id|le,让一群 AI 小生物在网页中生活,并提供故事模式。项目新增 Gloamhold 场景,以雾天开局、情绪更阴郁,pages 和 labs 则从 Home 设置的副本启动。世界状态通过 localStorage 持久化保存。

Hacker News 最新nico2026/11/4 · 5 天前30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

const KEEP_WORLD = { ...DEFAULT_WORLD, id: 'keep-0', weather: 'fog', wint: 0.55 }; let worldAt = 'home'; const wkey = b => worldAt === 'home' ? b : b + '@' + worldAt; let lastWorldText = null; if (!world.features) world.features = genFeatures(world); function saveWorld() { try { localStorage.setItem(wkey('arena.world'), JSON.stringify(world)); } catch (e) {} const el = $('#worldInfo'); if (el) el.textContent = ${world.id} · ${world.layout} · tilt ±${world.tilt ?? 18}° · ${world.font} ${world.size}px · ink ${world.inkSat ? 'hue ' + world.ink : 'plain'} · weather ${WEATHER_NAMES[world.weather] || world.weather} ${Math.round((world.wint ?? .35) * 100)}%; const wl = $('#weatherLabel'); if (wl) wl.textContent = '☁ ' + (WEATHER_NAMES[world.weather] || world.weather); if (window.__weather) window.__weather.set(world.weather, world.wint ?? .35, (world.wx || {})[world.weather]); } function worldFont() { return { serif: "Georgia,'Times New Roman',serif", mono: 'ui-monospace,Menlo,monospace', sans: 'system-ui,sans-serif' }[world.font] || 'Georgia,serif'; } function keepEvolved(told) { if (told && told.length) say('lab', '🕯️ Gloamhold evolved: ' + told.join('; '), 'lab'); } function genFeatures(w) { const r = () => Math.random(), F = []; const n = w.featureCount ?? 5; for (let i = 0; i +(v * Math.exp(gauss() * r)).toFixed(2); const w = { ...world, id: 'world-' + Date.now().toString(36).slice(-4) }; if (Math.random() x !== world.weather)); w.wint = Math.max(0.1, Math.min(1, +((world.wint ?? 0.35) * Math.exp(gauss() * 0.35)).toFixed(2))); if (Math.random() f.len / 2 - 8) { w.x -= ca * f.len * 0.98; w.y -= sa * f.len * 0.98; } } } // packet stream: a conveyor that loops else if (f.type === 'hole' && Math.hypot(dx, dy) hw + ext || Math.abs(y - cam.y) > hh + ext) continue; if (f.type === 'stream') { const ca = Math.cos(f.a), sa = Math.sin(f.a), x1 = x - ca * f.len / 2, y1 = y - sa * f.len / 2, x2 = x + ca * f.len / 2, y2 = y + sa * f.len / 2; s += ``; } else if (f.type === 'well') { for (let k = 0; k ; s += latency ${Math.round(f.r * 3)}ms; } else if (f.type === 'hole') s += 404; else s += cache · no wind; } return s; } function layTerrain(text, title) { lastWorldText = { text, title }; return layPage(text, title, document.getElementById('additive') && document.getElementById('additive').checked && words.some(w => w.terrain)); clearTerrain(); const r = arenaEl.getBoundingClientRect(), W = r.width, H = r.height; const sentences = String(text).replace(/\s+/g, ' ').split(/(? se.split(' ').map(w => ({ raw: w.replace(/^[^\w']+|[^\w']+$/g, ''), }) ).filter(o => /^[A-Za-z][A-Za-z'-]{0,17}$/.test(o.raw))).filter(a => a.length); let flat = []; sentences.forEach((se, si) => se.forEach(o => flat.push({ ...o, si }))); flat = flat.slice(0, 220); if (!flat.length) return; const freq = {}; flat.forEach(o => { const k = o.raw.toLowerCase(); freq[k] = (freq[k] || 0) + 1; }); const nIsl = Math.max(2, Math.min(7, Math.round(flat.length / 35))), centers = []; for (let i = 0; i Math.hypot(o.x - c.x, o.y - c.y))) : 1e9; if (d > bd) { bd = d; best = c; } } centers.push(best); } const per = Math.ceil(flat.length / nIsl), counts = new Array(nIsl).fill(0); centers.forEach((c, i) => { const isl = document.createElement('div'); isl.className = 'island'; const rad = 40 + Math.sqrt(per) * 13; isl.style.cssText = left:${c.x - rad}px;top:${c.y - rad * 0.8}px;width:${rad * 2}px;height:${rad * 1.6}px;border-radius:${45 + Math.random() * 10}% ${55 - Math.random() * 10}% ${50 + Math.random() * 8}% ${48 - Math.random() * 8}%;transform:rotate(${(Math.random() - .5) * 40}deg); floor.insertBefore(isl, floor.firstChild); }); flat.forEach((o, n) => { const i = Math.min(nIsl - 1, Math.floor(n / per)), c = centers[i], k = counts[i]++, ang = k * 2.39996 + i, rad = 18 * Math.sqrt(k + 0.6); const x = Math.max(16, Math.min(W - 16, c.x + Math.cos(ang) * rad * 1.35 + (Math.random() - .5) * 6)), y = Math.max(14, Math.min(H - 14, c.y + Math.sin(ang) * rad * 0.95 + (Math.random() - .5) * 6)); const low = o.raw.toLowerCase(), ww = addWord(low, x, y); ww.terrain = true; ww.el.classList.add('terrain'); const kind = GRASS.has(low) || low.length = 8 ? 'rock' : 'soil'; ww.kind = kind; ww.el.classList.add('t-' + kind); if (freq[low] > 1 && kind !== 'grass') ww.el.style.fontSize = Math.min(18, 11 + freq[low] * 1.5) + 'px'; ww.rot = (Math.random() - .5) * (kind === 'grass' ? 30 : 8); place(ww); }); say('lab', 'world: ' + (title ? '"' + title.slice(0, 60) + '", ' : '') + flat.length + ' words on ' + nIsl + ' islands', 'lab'); logEvent('terrain', { n: flat.length, islands: nIsl, url: !!title }); oracleComment(pick([New world. ${nIsl} islands of words, and the legs have noticed, Someone grew a landscape in my room. The crawler will eat half of it, My room has islands now. ${title ? 'They used to be ' + title.slice(0, 40) : 'Do not ask me to read them'}`]), 'terrain'); // meaning map: words that keep company in the corpus settle together (the chat page's 32-d co-occurrence vectors); each group of // meaning becomes a landform with its own shape — mound, ring, river, spiral, patch — so every page grows a different but consistent world let ASSOC = null; fetch('assoc_v1.json').then(r => r.json()).then(a => { a.idx = {}; a.words.forEach((w, i) => { a.idx[w] = i; }); ASSOC = a; }).catch(() => {}); function layMeaning(toks, put, patch, mound, rnd, rot, W, H) { if (!ASSOC) { toks.forEach(raw => put(raw, rnd(0, W), rnd(0, H), rot())); return; } const vecOf = w => { const i = ASSOC.idx[w.toLowerCase()]; return i == null ? null : ASSOC.vec[i]; }, D = ASSOC.dim; const items = toks.map(raw => ({ raw, v: vecOf(raw) })), known = items.filter(o => o.v); const K = Math.max(3, Math.min(9, world.mounds || 6)); let cent = known.slice().sort(() => Math.random() - .5).slice(0, K).map(o => o.v.slice());

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →