feat(polaris): 《山海行纪》表现层二轮——地面弃水岛主铺治重复+暂停空槽降重

创始人真机眼验批⑤⑥。⑤ 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) <noreply@anthropic.com>
This commit is contained in:
lili 2026-07-08 04:35:44 -07:00
parent 462946518f
commit a9c0367f6d

View File

@ -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';