From a9c0367f6d4fa6400d58e0fec3e5d66c6bb1ec2d Mon Sep 17 00:00:00 2001 From: lili Date: Wed, 8 Jul 2026 04:35:44 -0700 Subject: [PATCH] =?UTF-8?q?feat(polaris):=20=E3=80=8A=E5=B1=B1=E6=B5=B7?= =?UTF-8?q?=E8=A1=8C=E7=BA=AA=E3=80=8B=E8=A1=A8=E7=8E=B0=E5=B1=82=E4=BA=8C?= =?UTF-8?q?=E8=BD=AE=E2=80=94=E2=80=94=E5=9C=B0=E9=9D=A2=E5=BC=83=E6=B0=B4?= =?UTF-8?q?=E5=B2=9B=E4=B8=BB=E9=93=BA=E6=B2=BB=E9=87=8D=E5=A4=8D+?= =?UTF-8?q?=E6=9A=82=E5=81=9C=E7=A9=BA=E6=A7=BD=E9=99=8D=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创始人真机眼验批⑤⑥。⑤ drawGround 病根=base 段(0-15)本是水面小岛母题、满铺=规则重复小岛;改大尺度分区选帧(~40%青绿矿岩32-47/余深岩16-31穿插)+按位翻转+drawGroundShade 极轻云影+装饰补至8种加密,弃水岛主铺。⑥ drawSlotGrid 空槽改极淡底+虚线边+drawLockGlyph 锁形微标降重、已填略实突出。render-evidence fellBack=0(tiles/panel 真接线)、build-report 首屏 350702B PASS、headless-smoke S1/S2/S3 零回归。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../games/shanhai-xingji/src/render.js | 105 ++++++++++++++---- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/game-runtime/games/shanhai-xingji/src/render.js b/game-runtime/games/shanhai-xingji/src/render.js index cf5d23cf..8f37b825 100644 --- a/game-runtime/games/shanhai-xingji/src/render.js +++ b/game-runtime/games/shanhai-xingji/src/render.js @@ -197,28 +197,45 @@ function drawLevelupTitle(g, v, picker, viewport) { /** 地纹:真 tile 铺地(8 变奏按世界哈希选)+ 稀疏装饰物;缺图回退哈希撒点。 */ function drawGround(g, camX, camY, viewport, art) { if (art && art.has('deco-rock-01')) { - // 真 tile 铺地:128px 世界格,base 变奏(0..15)按格哈希选,少量暗/刻痕变奏点缀。 + // 真 tile 铺地:128px 世界格。选帧口径 2026-07-08 修——原以 base 段(0..15)为主铺, + // 但该段美术母题是「水面小岛」,满铺=规则重复小岛(真机眼验「同一水岛贴图网格平铺」的假)。 + // 改大尺度地貌分区:成片深岩(16..31) / 成片青绿矿岩(32..47)穿插,一暗一亮互为层次 + //(深岩沉稳、青绿提亮兼解深岩单铺过暗),读作「深岩地里的青绿矿脉/湿岩带」。水岸段(0..15)弃。 const T = 128; + const OVER = 8; // 外扩重叠(原 2→8):加大压住 tile 非无缝边界的接缝 + const REGION = 384; // 地貌分区尺度(3×3 tile 一片):同一片连成深岩 or 青绿矿岩 const x0 = Math.floor(camX / T) * T, y0 = Math.floor(camY / T) * T; for (let wy = y0; wy < camY + viewport.h + T; wy += T) { for (let wx = x0; wx < camX + viewport.w + T; wx += T) { const h = ((wx * 374761393 + wy * 668265263) ^ 0x5bf03635) >>> 0; - // 主体 base 0..15;1/8 概率取暗变奏(16..31),1/16 取刻痕(32..47) - let idx = h % 16; - if ((h >>> 8) % 8 === 0) idx += 16; - else if ((h >>> 12) % 16 === 0) idx += 32; - // 轻微外扩 3px 重叠相邻格,压住 tile 非无缝边界的接缝线(读作整片岩地而非拼块) - art.drawTile(g, idx, wx - camX - 2, wy - camY - 2, T + 4); + // 大尺度分区哈希决定这一片是深岩还是青绿矿岩(而非每格胡椒面撒→避免噪点/打架) + const rgx = Math.floor(wx / REGION), rgy = Math.floor(wy / REGION); + const rh = ((rgx * 2654435761 + rgy * 40503) ^ 0x9e3779b1) >>> 0; + const idx = ((rh % 5) < 2 ? 32 : 16) + (h % 16); // ~40% 片区青绿矿岩,余深岩 + // 确定性 4 向翻转:同段 16 帧再乘 4 朝向,打散岩纹方向对齐的机械网格感(位提取只用 >>>) + const fx = (h >>> 16) & 1, fy = (h >>> 17) & 1; + const dx = wx - camX - OVER, dy = wy - camY - OVER, size = T + OVER * 2; + if (fx || fy) { + g.save(); + const cxp = wx - camX + T / 2, cyp = wy - camY + T / 2; // 以 tile 显示中心为翻转轴 + g.translate(cxp, cyp); g.scale(fx ? -1 : 1, fy ? -1 : 1); g.translate(-cxp, -cyp); + art.drawTile(g, idx, dx, dy, size); + g.restore(); + } else { + art.drawTile(g, idx, dx, dy, size); + } } } - // 稀疏装饰物(岩/图腾/骨/苔;世界哈希定位,确定性) - const DECO = ['deco-rock-01', 'deco-rock-02', 'deco-totem-01', 'deco-moss-01', 'deco-bone-01', 'deco-reed-01', 'deco-runestone-01']; - const D = 384; // 装饰稀疏格 + // 大尺度地貌明暗:极轻径向光斑柔化(分区已提供主明暗,这里只轻抹起伏,治残余均匀感) + drawGroundShade(g, camX, camY, viewport); + // 稀疏装饰物(岩/图腾/骨/苔/芦苇/符石;补第 8 种 deco-totem-02;加密到约 2/5;世界哈希定位) + const DECO = ['deco-rock-01', 'deco-rock-02', 'deco-totem-01', 'deco-totem-02', 'deco-moss-01', 'deco-bone-01', 'deco-reed-01', 'deco-runestone-01']; + const D = 288; // 装饰格(原 384→288 加密,配合遮挡打断网格) const dx0 = Math.floor(camX / D) * D, dy0 = Math.floor(camY / D) * D; for (let wy = dy0; wy < camY + viewport.h + D; wy += D) { for (let wx = dx0; wx < camX + viewport.w + D; wx += D) { const h = ((wx * 2246822519 + wy * 3266489917) ^ 0x9e3779b1) >>> 0; - if (h % 3 !== 0) continue; // 约 1/3 格有装饰 + if (h % 5 < 3) continue; // 约 2/5 格有装饰(原 1/3→加密) const name = DECO[h % DECO.length]; const px = wx + (h % 200) - camX, py = wy + ((h >>> 10) % 200) - camY; art.draw(g, name, px, py, 46 + (h >>> 4) % 26, { anchor: 'bottom', alpha: 0.92 }); @@ -240,6 +257,32 @@ function drawGround(g, camX, camY, viewport, art) { } } +/** + * 大尺度地貌明暗:确定性径向光斑铺屏(约半数暗坡 + 半数受光金斑),世界坐标定位随镜头滚动。 + * 大尺度格(512)远大于 128 tile、且不与格线对齐;平滑径向渐变叠在 tile 之上,给地面明暗 + * 起伏——治「整片死均匀像铺满壁纸」这层假。每屏约 10 余个光斑,createRadialGradient 便宜。 + * 【负半径纪律】半径恒过正偏移(260+…),不会算出负值。 + */ +function drawGroundShade(g, camX, camY, viewport) { + const SH = 512, MARGIN = 440; // 明暗大尺度格 + 光斑外扩边界(覆盖屏外半个光斑) + const x0 = Math.floor((camX - MARGIN) / SH) * SH, y0 = Math.floor((camY - MARGIN) / SH) * SH; + for (let wy = y0; wy < camY + viewport.h + MARGIN; wy += SH) { + for (let wx = x0; wx < camX + viewport.w + MARGIN; wx += SH) { + const h = ((wx * 2654435761 + wy * 40503) ^ 0x27d4eb2f) >>> 0; + const cx = wx + (h % SH) - camX, cy = wy + ((h >>> 9) % SH) - camY; // 格内抖动散布 + const r = 260 + ((h >>> 4) % 170); + const grad = g.createRadialGradient(cx, cy, 0, cx, cy, r); + if (((h >>> 20) & 1) === 0) { // 暗坡:极淡(大尺度明暗已由青绿/深岩分区提供,这里只轻柔化) + grad.addColorStop(0, 'rgba(6,10,8,0.14)'); grad.addColorStop(1, 'rgba(6,10,8,0)'); + } else { // 受光坡:极淡描金 + grad.addColorStop(0, 'rgba(216,160,62,0.08)'); grad.addColorStop(1, 'rgba(216,160,62,0)'); + } + g.fillStyle = grad; + g.fillRect(cx - r, cy - r, r * 2, r * 2); + } + } +} + function drawPlayer(g, v, viewport, art) { const x = viewport.w / 2, y = viewport.h / 2; const blink = v.player.iframe > 0 && ((v.frame >> 2) & 1) === 0; @@ -766,17 +809,28 @@ function drawSlotGrid(g, viewport, y0, label, slots, art) { const cx = px + 16 + (i % cols) * (cw + 8); const cy = y0 + 10 + Math.floor(i / cols) * (ch + 8); const sl = slots[i]; - // 槽底:半透明暗底 + 描金边(ui-slot-item 方形圆徽拉成宽格会糊,故用暗底+边+图标保可读) - g.fillStyle = sl ? 'rgba(12,9,4,0.55)' : 'rgba(12,9,4,0.32)'; - g.strokeStyle = sl ? (sl.evolved ? C.player : 'rgba(216,160,62,0.6)') : 'rgba(232,220,192,0.2)'; - g.lineWidth = sl && sl.evolved ? 2 : 1.5; - if (g.roundRect) { g.beginPath(); g.roundRect(cx, cy, cw, ch, 8); g.fill(); g.stroke(); } - else { g.fillRect(cx, cy, cw, ch); g.strokeRect(cx, cy, cw, ch); } + // 空槽 vs 已填槽分绘(修「开局大片等重空虚线框显空」):空槽极淡底 + 虚线细边 + 锁形微标 + // (视觉降重、语义「待获取」);已填槽实底 + 描金边——空槽退到背景,已填自然突出。 + // ui-slot-item 方形圆徽拉成宽格会糊,故仍用暗底+边+图标保可读。 if (!sl) { - g.fillStyle = 'rgba(232,220,192,0.28)'; g.font = '14px system-ui, sans-serif'; g.textAlign = 'center'; - g.fillText('—', cx + cw / 2, cy + ch / 2 + 5); + g.fillStyle = 'rgba(12,9,4,0.20)'; // 底比原 0.32 更淡,进一步降重 + if (g.roundRect) { g.beginPath(); g.roundRect(cx, cy, cw, ch, 8); g.fill(); } + else g.fillRect(cx, cy, cw, ch); + g.save(); + g.strokeStyle = 'rgba(232,220,192,0.13)'; g.lineWidth = 1; // 虚线细边,弱到近乎背景 + if (g.setLineDash) g.setLineDash([4, 4]); + if (g.roundRect) { g.beginPath(); g.roundRect(cx + 0.5, cy + 0.5, cw - 1, ch - 1, 8); g.stroke(); } + else g.strokeRect(cx + 0.5, cy + 0.5, cw - 1, ch - 1); + g.restore(); + drawLockGlyph(g, cx + cw / 2, cy + ch / 2, 'rgba(232,220,192,0.24)'); // 极简锁形替代「—」 continue; } + // 已填槽:实底 + 描金边(evolved 更亮更粗),底/边都比原来略实以对比空槽 + g.fillStyle = 'rgba(12,9,4,0.62)'; + g.strokeStyle = sl.evolved ? C.player : 'rgba(216,160,62,0.72)'; + g.lineWidth = sl.evolved ? 2 : 1.5; + if (g.roundRect) { g.beginPath(); g.roundRect(cx, cy, cw, ch, 8); g.fill(); g.stroke(); } + else { g.fillRect(cx, cy, cw, ch); g.strokeRect(cx, cy, cw, ch); } // 符器图标(左侧小图) if (sl.icon && art) art.draw(g, sl.icon, cx + 20, cy + ch / 2, ch - 14); g.fillStyle = sl.evolved ? C.player : C.hud; g.font = 'bold 12px system-ui, sans-serif'; g.textAlign = 'center'; @@ -788,6 +842,19 @@ function drawSlotGrid(g, viewport, y0, label, slots, art) { g.textAlign = 'left'; } +/** 极简锁形微标(空槽「待获取」语义;纯程序绘制,色带极淡 alpha,不喧宾夺主)。 */ +function drawLockGlyph(g, cx, cy, color) { + g.save(); + g.strokeStyle = color; g.lineWidth = 1.5; + // 锁梁:开口朝下的半圆环(anticlockwise=true 取上拱) + g.beginPath(); g.arc(cx, cy - 3, 4, Math.PI, 0, true); g.stroke(); + // 锁体:圆角小矩形描边 + g.beginPath(); + if (g.roundRect) g.roundRect(cx - 6, cy - 1, 12, 9, 2); else g.rect(cx - 6, cy - 1, 12, 9); + g.stroke(); + g.restore(); +} + function drawSettle(g, v, viewport, best, art) { g.fillStyle = 'rgba(10,8,4,0.80)'; g.fillRect(0, 0, viewport.w, viewport.h); const win = v.phase === 'win';