Reviewed-on: #1
山河拼图
参考用户提供的日本地图下落合并视频开发的小红书离线小工具。正式交付包含省级版、地级版,以及四直辖市县区、港澳分区、台湾县市细分关卡。新增329个地级行政区的县区关卡,共364关,支持全国→省域→城市县区的三级选关。
回正辅助现为“回正一次”按钮:只调整点击时已落下的地块,没有持续模式,新块和后续碰撞仍自由转动。
项目独立存放于 ProjectRednote/shanhe-puzzle/。以下命令请在 shanhe-puzzle 目录执行。仓库仅跟踪源码、测试和构建脚本、依赖清单、本文档,以及运行和构建校验必需的地图数据。
立即预览和打包
npm ci
npm run build
npm run dev
# http://localhost:4173
npm test
npm run build
npm run test:browser
npm run test:staging
browser/staging为当前手动落子与难度确认流程;其余旧浏览器脚本中依赖定时下落或直接开局的场景需要更新,不列入上述建议命令。本次通过CUA实际操作验证界面,未运行CLI浏览器脚本。浏览器测试目前默认使用 macOS 已安装的 Google Chrome,路径见 tests/browser.mjs。构建要求 Python 3,以及官方 minitool-zip-builder 1.8.0 Skill。克隆后需将 Skill 解压到本地 .codex/skills/minitool-zip-builder/,该目录应直接包含 SKILL.md、references/ 和 scripts/。本地工具、依赖、构建输出和报告均已忽略,不推送到代码库。所有运行依赖与地图随最终 ZIP 提供,无联网运行逻辑。
上传产物:artifacts/shanhe-puzzle-xhs.zip。根目录直接包含 index.html,不是项目源码压缩包。源代码在 src/,静态构建在 dist/。发行包共186个文件:173个同省双关县区脚本和13个基础文件,保留全部364关。预检和打包器均强制限制文件数不超过200,不包含node_modules、.git、原始素材或测试报告。
必须在创服平台模拟器、Android 真机扫码、iOS 真机扫码验证同一 ZIP。 本地浏览器仅用于开发回归和视觉检查,不能替代小红书容器验收。Chrome 61 / Android 8.1 兼容性、CSS 兼容性及真机性能均未实测;原生发笔记、相册授权和保存需要真机验证。
本机预览默认提供dist中的构建产物;修改源码后需先重新运行npm run build。服务监听 127.0.0.1:4173,可以在这台Mac的系统浏览器打开 http://localhost:4173/;与Codex内置浏览器使用同一个服务。当前已有服务时无需重复启动。
版本范围
- 省级版:全国 34 个省级行政区,各出现一次,合并为一个整体即通关。
- 地级版:选择省或自治区,提供27个省内关卡、333个地级行政区及30个省直辖县级地区,合计363块,包含地级市、自治州、地区、盟和省直辖地区。
- 省内关卡保留省级源素材的全部直接子区域,补回湖北仙桃、潜江、天门、神农架(13→17块),海南15个省直辖地区(4→19块),河南济源(17→18块),新疆素材中的10个直属市(14→24块)。这四关标为“省域拼图”,说明包括省直辖县级地区;不向下展开普通地级市所属县区。范围以2026-10-16下载的源素材快照为准,不声称当前最新区划完备性。
- 北京、天津、上海各16区,重庆38个区县:显示“县区拼图”。香港18个分区、澳门8个源素材分区:显示“分区拼图”,不冒称县级行政区。
- 台湾省20个县市:显示“县市拼图”,不与市内各区混级。来源为国土测绘中心COUNTY_MOI_1090820开放快照,2026-10-17下载,遵循政府资料开放授权条款第1版。金门、连江属福建,不重复列入台湾关卡;所有源外环保留,远海岛屿移位示意在准备页明确标注。
- 全国入口不显示返回按钮,只有进入细分地图后才显示“全国地图”;导航行保留固定高度,出现或隐藏按钮不改变地图主体、开始按钮位置。
- 本机县级原始边界位于
data/raw/,共 335 组、2840 个县级单元;预处理素材位于data/county-materials/,另附独立素材 ZIP。原始数据与备用素材均保留本地,不进入Git;正式包和Git仅包含已开放关卡所需的简化轮廓。
玩法与得分
进入页以中国地图为主体,点击“开启旅程”并确认难度后开始全国34块省级拼图;点击地图上的省区即可钻入对应地级、县区、县市或分区拼图准备页;省域地图上的地级市可再进入该市县区拼图,依次返回省域和全国。地图上的名称也能点击,支持键盘聚焦后按 Enter / 空格。原先未开放的7个地区现已按县区、县市或分区层级开放;34个省级地区均可进入细分关卡。
“这次要拼 N 块山河”、目的地和本地最佳与地图放在同一个场景里。地图场景、开局栏按可见视口确定固定尺寸,选省、返回、打开或关闭设置均不重新分配高度。首页难度仅作展示,吸附难度和碰撞弹性仅在点击“开启旅程”后的确认弹窗内修改,确认出发后生效,取消不修改。每次开启、重新开局或再来一局,都必须确认难度;确认前不启动或重置游戏,取消重开返回原局暂停页。所有对局固定为随心落子,不再提供下落速度选择。吸附默认“轻松拼”,弹性保留四档。
进入游戏后,首块及后续每一块先在红线上方准备,尚未进入物理世界,不计入落下数、合并或堆顶判断。按住上方待落区左右拖动可调整下一块横向落点,按真实轮廓限制左右边界;预览按预备区空间显示,小地区有最小可见尺寸。释放后按当前游戏比例进入物理世界。
所有关卡均为“随心落子”:等待不会自动释放,在红线上方左右调整并松手时才落一块,得分倍率×1。取消触摸、暂停、切后台、横竖屏变化不视为松手落块;暂停冻结游戏计时。旧手势不能继续拖动已落图块或误放下一块。电脑聚焦画布后可按Enter辅助释放待落块。
在红线下方快速轻划并松手,给容器一次惯性晃动;划过位置附近的地块反应更强。每次轻划只在松手时触发一次,地块获得带少量方向、力度差异的冲量后自由运动,手指只能决定大致方向。慢拖、按住、取消触摸或暂停不会持续施力,也不会抓取、牵引或瞬移单块碎片。底部只有一个无文字的圆形暂停图标,统一查看本局旅程、继续、结束或重开;电脑保留方向键辅助。弹跳高度、平移速度和旋转速度有上限,旋转角度不受限制。
全国版中,同一组合包含一个玩法地区的全部省级地块时,触发一次“地区拼合完成”庆祝:金色轮廓、短暂提示卡和少量彩屑。七组为华北、东北、华东、华中、华南、西南、西北,互不重复覆盖34块;华东含台湾,华南含香港、澳门。仅为玩法分组,完整名单见src/game.js。一个地区可以属于更大的组合,只落齐而未合并不触发。每组每局仅一次,连续达成依次展示。细分关卡按最大组合达到25%、50%、75%提示“初见山河 / 半程相逢 / 山河渐全”;少于两块或已经相当于全关的门槛略过,一次跨多个门槛只展示最高新阶段。阶段提示不额外加分。
全部拼完后播放2.6秒“山河,完整了”庆祝,再进入成绩页;物理、得分和游玩计时冻结,切后台暂停演出、返回继续。游戏内演出及阶段反馈使用同一Canvas绘制器进入完整游玩视频,视频保留通关演出时长,成绩图的游玩时长不增加。尊重减少动态效果设置,保留静态达成提示。
只有真实相邻关系、正确地理位置接近时才自动吸合。全部落下后仍可晃动,直到通关、同一组地块稳定堆过红线超过 2.4 秒(出生保护 5 秒,须经实际接触连到底部;短暂弹起、贴侧墙或悬空不计),或主动结束。底部圆形暂停图标打开旅程菜单,可继续、结束旅程、重新开局。结束旅程会保留成绩,即使零分或未通关也会生成图片;通关和判负自动生成。
省级海岛使用明确标注的“跨海连接”;地级版中因海域、源素材中不相接地区等产生的间隔,使用明确标注的“隔区连接”。这两种连接都是游戏规则,不能当作陆地邻接关系。入口地图保留相对地理位置。
| 吸附难度 | 合并位置容差 | 开始靠拢范围 | 相对角度容差 |
|---|---|---|---|
| 轻松拼(默认) | 84px | 148px | 约57° |
| 刚刚好 | 52px | 98px | 约38° |
| 严丝合缝(原规则) | 26px | 45px | 约16° |
距离为游戏Canvas逻辑像素。轻松和普通档围绕真实邻接的地区中心中点比较正确位置,避免地图原点影响角度容差;困难档保留此前的平移/角度判定。远距靠拢仍只施加双向力,不旋转回正,不直接拖动已落地块。所有档位都保留合并试放、长大阻挡和防穿透检查。
每次合并分数 = (100 + 40 × (合并后块数 − 2)) × 连合系数 × 特殊连接系数,四舍五入到整数。8.5 秒内再次合并视为连合,系数每次增加 0.25,从 1 起、最高 2;跨海和隔区连接额外乘 0.8。通关再奖 1000 分。轻划不限次数、不扣分。吸附难度和弹性不改变得分倍率。
弹性四档为“轻轻弹 / 蹦蹦跳 / 元气弹跳 / 弹力全开”,碰撞恢复系数分别为 0.65 / 0.82 / 0.90 / 0.96,同时调整空气阻力、横向冲量和上抛上限。新档“元气弹跳”的上抛、冲量和速度上限介于正常与全开之间;保留旧三档参数和纪录身份;默认推荐“蹦蹦跳”,便于腾挪,“弹力全开”需要更好地控制节奏。省级、地级碎片均在上一版基础上线性再缩小 10%;地级仍比相同地图基准额外小 30%;组合过大时仍会全局等比缩放。
最佳纪录按“关卡 + 吸附难度 + 弹性”分别保存;旧版随心落子纪录只归入“严丝合缝”及其原弹性档位,不混入较易难度;旧版定时纪录仍保留但不混入当前模式的入口最佳;战绩簿按难度汇总历史最高分,旧版无吸附档位的分数归入原难度。客户端 9.46.0+ 优先使用官方 Storage;含同步启动信息、异步回退、编译序号剥离及旧纪录迁移。旧环境回退到带异常处理的浏览器存储。
成绩页展示可分享的 750×1400 图片:本局得分、结束时实际容器画面、关卡、吸附难度和弹性、用时、最大拼合 / 最佳连合,以及得分随分钟变化的曲线。曲线从零开始,记录实际得分时刻和结束时间,包含通关奖励,暂停不计时。不会用完整地图冒充未完成的结局。
“发小红书”在玩家点击后调用 writeTempFile + postNote,携带图片和成绩文案打开图文发布页,玩家可编辑、取消、确认发布;不会自动发布或声称审核成功。“保存成绩图”调用 writeTempFile + saveImageToPhotosAlbum。无 SDK、接口缺失、授权拒绝、取消或调用失败都有提示,不丢失成绩图;临时路径即用即弃。
成绩页以斜线分隔的统一发布入口提供“发图文 / 发视频”。首次点击“发视频”会生成视频并继续打开发布编辑页,已有视频则直接复用;生成期间右侧变为“取消生成”。发布时用 writeTempFile 写入 MP4 与成绩图封面,再通过 postNote({pageType: "video_publish"}) 打开视频笔记编辑页。玩家确认后发布;失败或取消保留生成的视频,重试时使用新临时路径。当前设备不支持 MP4 编码时提示并保留成绩图分享。
视频来自整局游戏的轻量渲染状态:从开局到结束按30帧/秒采样图块位置、旋转、合并缩放、待落图块、碰撞效果与得分,暂停和后台不计入旅程时间;共享离线地图几何,不截取或压缩JPEG。结束后直接用同一套地图绘制器在720×1280画布重绘,以30帧/秒、目标3Mbps编码无声H.264 MP4,尾部附2秒真实成绩图。时间线不再限制为最后60秒。状态存储按32MiB估算预算控制,超预算时均匀降低整条时间线的运动采样率,始终保留开头与结尾,界面提示实际采样率;输出仍为30帧/秒,低采样率时重复最近状态,不伪称新增运动细节。输出48MiB安全上限是应用自身的内存保护,非平台配额;超限明确报错,不截断过程。仅在发布时短暂转换为原生接口要求的base64。不记录相机、麦克风、手机屏幕或通知,不调用屏幕共享。没有本地视频播放预览或网页下载,避免容器禁止的blob/data视频媒体源。
优先检测 VideoEncoder.isConfigSupported 的H.264能力,使用WebCodecs与随包离线编译的mp4-muxer 5.2.2生成标准MP4;检测失败或编码失败时,仅在 MediaRecorder.isTypeSupported 明确支持MP4时采用画布流备用编码。备用路径约需与整局时长相同的生成时间;超时预算随整局时长延长;不把WebM改后缀伪装为MP4。两条路径均可取消,切后台或离开成绩页会中止并释放编码器、视频轨道及画面引用。新开局或返回入口清除上一局素材。WebCodecs及MP4录制是可选增强,旧WebView保留游戏与成绩图,不承诺视频可用。
npm run test:video 生成两条路径的真实MP4并用ffprobe/ffmpeg核对H.264、尺寸、时长及完整解码,验证原生接口参数(模拟SDK)、发布失败重试、无SDK/无编码能力降级、取消/后台/新开局清理、60秒/6MiB采样边界与小屏布局。现代Chrome结果不能证明小红书容器可用;Android/iOS的实际编码权限、速度、内存和video_publish全链路均待扫码验证。
构建使用已随源码提供的 src/maps.js;research/china.json 是构建时全国地图完整性核对必需的源快照;research/detail-map-index.json 是7个细分关卡的源代码/名称/内外环数量及来源摘要,供构建审计使用。其余 research/ 文件和整个 data/ 目录均不入库。下载与预处理脚本仅用于另行更新地图;重生成前需要准备 research/areas.json 等原始输入。
代码与数据
src/core.js:相邻关系、地理位置与角度吸合判断、计分、纪录清洗。src/game.js:Matter.js 刚体、出片、组合、胜负、速度、弹性、容器滑动与上晃、得分时间点和尺寸变化。src/app.js:入口 / 游戏 / 成绩页切换、Canvas 游戏绘制、SVG 地图选关、难度弹窗、容器手势与键盘输入、暂停与结束、原生 Storage、发笔记和相册导出。src/result.js:真实结局快照与得分曲线的成绩图绘制。src/video.js:有界游戏画面采样、MP4编码能力检测、两条本地编码路径、取消及资源清理。src/index.html、src/style.css:单入口、响应式布局、安全区和 Chrome 61 样式基线。src/maps.js:省级、地级、指定县区/县市/分区真实轮廓与连接图;有内环的区域包含构建期三角剖分碰撞网格。scripts/download-maps.py、scripts/download-provinces.py:仅开发期下载公开地图;后者有台湾省 404 的如实记录。scripts/prepare-maps.py、scripts/map_geometry.py:共享投影、简化、邻接和标签布局,输出省级/地级数据及备用县级素材。scripts/prepare-taiwan.py、scripts/prepare-detail-maps.py、scripts/triangulate-holes.mjs:将本地官方SHP素材转为县市输入,增加7个指定细分关卡;保留内部空洞,生成不填满内嵌城区的碰撞几何。Earcut 2.2.4仅作为构建依赖,不在游戏运行时加载。scripts/check.mjs、scripts/build.mjs、scripts/package.py:ES2017 / 离线静态检查、官方 Skill 体积审计、ZIP 结构及完整性验证。tests/:真实地图可通关性、错误边拒合、朝向一致性、计分、警戒线、整框惯性及入口 / 开局浏览器回归。
全国、地级及四直辖市/港澳地图来源:阿里云 DataV.GeoAtlas,2026-10-16 下载。数据可能落后于最新行政区划,已做游戏用轮廓简化,不能用于导航、测绘或行政区划查询。台湾县市补充来源:国土测绘中心开放县市边界,原始ZIP为COUNTY_MOI_1090820快照,政府资料开放授权条款第1版。原始数据提供方条款继续适用。视觉保留岛屿轮廓;物理碰撞对极小区域、退化或相交轮廓采用简化代理。
物理引擎 Matter.js 0.20.0、凹多边形分解 poly-decomp 0.3.0 随包本地提供,MP4封装依赖mp4-muxer 5.2.2一并编译随包提供,许可证见发布包 licenses.json。不用 AI、外部 SDK、远程字体、WASM、Worker、网络请求、剪贴板或文件下载能力。
检查记录
artifacts/check-report.json 是本地语法及资源预检,artifacts/official-audit.txt 是官方 Skill 1.8.0 的目录与 ZIP 审计结果,artifacts/browser-report.json 是本地浏览器开发回归,artifacts/package-report.json 提供包大小和 SHA-256。详细交付检查及待验证项目见 artifacts/VALIDATION.md。
容器晃动与碎片比例更新
棋盘采用较窄的开口实体容器,侧壁和底边清晰可见,红色警戒线随容器高度在顶部62~120px适配。以320×435逻辑尺寸为例,全国开局缩放由0.4995调整到约0.4246,图块宽高再缩小15%。保持各省之间真实比例,微小地区保持真实相对比例,同样通过框体晃动参与拼接。
晃动采用阻尼弹簧模型:容器位移和施加给所有碎片的惯性来自同一状态,并有 14px 的视觉位移限制。在容器内滑动可朝任意方向施加冲量,力度随滑速增加,按与手指的距离平滑衰减;所有组合都受影响,近处更强。移动时只采集手势,在松手时识别一次轻划;使用最近约180ms轨迹,位移至少18px、平均速度至少0.16px/ms,慢拖和按住不触发。触发间隔至少240ms,系统取消或暂停清理尚未触发的手势。键盘左右键仍支持长按。无需加速度计或设备运动 API。
较大组合超出容器可用空间时,全体碎片按相同比例缩小,保持地理相对比例、分数与队列,继续容纳完整地图。难度和拥堵由关卡形状、晃动、速度及弹性共同影响;本地模拟只用于排查物理问题,不能推断真机性能或玩家通关难度。
碰撞与晃动手感
碰撞采用160ms轻微压缩回弹、淡轮廓与180ms小接触点,明显弱于合并时的1600ms反馈。渲染形变不修改真实碰撞几何。左右晃动缓存300ms颠起请求,每个物理步在实际接触时处理,避免150ms输入采样错过55ms接触窗口;每组仍有500ms颠起间隔,过期请求不在空中反复加速。暂停/离开页面清理待处理请求。上晃对框内全部组合施加一次向上冲量,420ms 冷却限制快速连点。颠起目标与上抛速度随弹性档位变化,但各档均有上限。地块自由旋转,不限制绝对角度、不自动回正;角速度上限为0.025弧度/物理步,水平和下落速度仍受限,避免无限加速或飞出框。
tests/shake-preview.mjs 用真实左右操作和虚拟时钟采集碰撞反馈、速度与截图;tests/results.mjs 检查上晃、暂停主动结束、曲线和图片生成、模拟 SDK 参数和失败降级、自然判负及小型关卡真实通关的结算流程。均为本地开发回归,不能替代平台模拟器或真机验收。
artifacts/bounce-probe.json记录320×590逻辑尺寸下的实际物理峰值:单块正方形、一次上晃,四种速度分别测得轻弹33~37px、正常97~110px、强弹236~270px。真实地图组合、容器尺寸和接触情况会改变结果。
全国/省区地图切换有轻微渐入,地图区域尺寸固定;弹性选项压缩回弹,普通按钮轻按,弹窗渐入。减少动态效果设置会关闭装饰动画与碰撞形变,保留游戏物理。npm run test:interactions覆盖地图钻入/返回、难度弹窗、推荐默认档、按钮动画和真实碰撞反馈。
手机布局与地图标签修正
顶部在状态栏安全区之外额外预留56px平台操作区。玩法说明移到入口页脚,游戏的旅程设置和暂停/结束移到底部,成绩页返回移入底部操作组;弹窗也避开平台顶部区域。
手机入口以可点击的SVG地图为主体,保留所有省级轮廓、离岛和海域线,省区准备页展示真实地级轮廓。片数与地图组成同一场景;不再使用可变高度的版本滑块、省区表单或独立选择卡片。全国与省区场景、难度入口和开局按钮位置固定,横屏时地图与信息并排。
入口和游戏页均按可见视口及安全区排版:320×568、375×667、390×844以及740×390横屏同屏显示。吸附难度和弹性移入同一弹窗,短屏按媒体查询收紧弹窗间距;设置不改变底层地图或开局栏的位置。
已移除定时速度选择,当前所有关卡均固定为随心落子。底部仅保留无文字的圆形暂停图标,合并全部旅程操作,容器顶部说明标签已移除,成绩页同样一屏显示。游戏地图先绘制全部形状,再统一绘制标签;名称采用独立布局、边距约束、避让,以及小地区标签底色 / 引线,防止名称贴底被裁或被其他省份覆盖。
构建补回源数据的小离岛外环及独立海域线层,34省级代码、名称及外环数量均与保存的原始数据对比。台湾省14个源外环、香港9个、澳门2个、海南133个全部保留。详见artifacts/MAP_AUDIT.md,这不是标准地图审图结论。
npm run test:container在现代浏览器模拟平台顶部按钮与四组手机尺寸/安全区,逐页点击并检查布局,不替代小红书真机验证。
tests/swipes.mjs 使用真实触摸事件验证单次轻划松手触发、移动中不施力、慢拖和按住过滤、松手后的惯性运动、系统取消、滑动中暂停和恢复、圆形无文字菜单;物理单测另检查附近力度更强、远处仍动、快速更有力和四档边界限制。
npm run test:atlas逐一从全国地图钻入34个省区并返回,检查34个细分关卡可玩并按层级标注、全国直接开局、省区11块真实开局和难度生效;严格比较地图场景、开局栏及按钮的位置/尺寸,确保各次选择不改变高度。tests/helpers.mjs封装通过可见地图与弹窗选择关卡/难度,供浏览器回归复用。
npm run test:staging使用真实CDP触摸检查:首块预备、上方横向调整、∞等待/松手、取消/暂停、定时到点强制释放、提前松手不加速、旧手势隔离、倒计时冻结,以及五个速度选项/四个弹性选项顺序和成绩难度字段。物理测试另验证四档时限、预备块不入物理世界及轮廓边界。
图块比例与吸附微调(2026-10-17)
游戏内省级、地级图块在前版基础上统一缩小15%,待落区和下一块预览同步缩小。地块名称同比缩小,最小保留8px,描边随字体收细;入口封面SVG地图与其名称保持原尺寸。正确相邻组合的位置误差允许从22px放宽到26px,相对角度误差从约13.8°放宽到16°,仍检查真实邻接和正确方位,过远、贴错边或非邻接不会合并。
细分地图素材更新
原始素材保存在忽略的data/provinces-raw/和data/taiwan-raw/中,正式构建直接使用已生成的src/maps.js与紧凑审计索引,不需要联网下载。需要重新生成地图时,先准备原始输入并运行python3 scripts/prepare-maps.py;从官方图资下载页下载县市SHP ZIP至data/taiwan-raw/counties.zip,再依次运行python3 scripts/prepare-taiwan.py、python3 scripts/prepare-detail-maps.py,最后npm run build。两个县市内嵌案例和天津两处内环都用静态三角网格保留空洞,SVG/Canvas使用偶奇填充,内嵌城市的源边界记为陆地邻接。
test:atlas检查34地区钻入/返回与固定几何尺寸,并在新增7关真实开局、暂停结束;test:container在手机/横屏/安全区下覆盖县区、县市、港澳分区。物理测试覆盖35个关卡529块及全部关卡可合并为整体,同时验证新北/台北、嘉义县/市的空洞碰撞。实际低端手机性能仍待扫码验证。
侧墙在 Matter 创建静态刚体后单独设为零摩擦,底部及地块间摩擦保持原值,避免不规则轮廓挂墙休眠。每100ms按位置和角度变化检查是否稳定,并用凸分片向下1.5px探测真实支撑链,保留地图空洞,不以复合包围盒充当支撑;没有底部支撑的休眠体会恢复下落。合并的边界修正只移动该组合,全局缩放仍统一作用于全部地块。tests/overflow.test.cjs覆盖真实江西90种贴墙姿态、窗口变化、悬空唤醒、多层休眠支撑、侧贴与空洞、无关地块位置;shanheDebug.state()含支撑/休眠/堆顶计时信息供偶发问题排查。
主动靠拢:已落地块在正确拼合位置的平移误差大于26px、不超过45px,且真实邻接、相对角度不超过约16°时,最近的不冲突配对受到轻微中心力,越近越强;不修改地块位置,不施加旋转,双向等大反向力按质量分配运动,限制沿拼合方向的助推速度,保留玩家惯性与物理碰撞。进入26px仍用原合并规则。预备块、暂停、远处、错向及非相邻地块不参与牵引。
地图块比例再次调整:PIECE_SIZE由0.85降至0.60,游戏内宽高为前版约70.6%、面积约49.8%;全国320px宽容器中的完整地图初始宽度从424.6px降至299.7px,内蒙古从199.1px降至140.6px。地级/县区/县市/分区关卡及待落、下一块轮廓同比缩小,地级相对全国的0.7比例保留;字体随比例调整但不低于8px。入口封面SVG保持原状。
大省与合并成长:全国关卡中,新疆、内蒙古、西藏、青海单独存在时再缩小8%,其他省区和细分关卡单块保持原比例。拼合判断使用统一比例的中心坐标,显示和碰撞共同使用每组实际比例;合并后按组合块数恢复统一比例并轻微长大(2块+2%、4块+4%、8块及以上最多+6%),320ms缓出动画逐步同步放大实际碰撞体与轮廓,角度保持自然状态。累计增长按块数计算,不逐次倍乘;先按最终大小检查容器边界,暂停冻结动画,缩放/横竖屏保留中间比例,通关截图直接使用最终尺寸。封面、源地图、计分及26/45px规则不变。
省域素材完整性由research/prefecture-map-index.json保留27组源子区域的代码、名称、类型及内外环计数,构建时逐一核对,防止省直辖地区再次被过滤。原始地图和对比截图仍保留在本地忽略目录。test:province-direct覆盖四个修复关卡的地图显示、新增地区真实释放、成绩片数及手机/横屏固定布局。
海南省域关卡:本岛放大,三沙诸岛全部源外环作为统一缩放、平移的示意插图保留;地图框及说明明确标注“缩放移位示意”,游戏碰撞与轮廓采用同一变换。全国海南轮廓、海域线和原始素材坐标保持原状。
合并、长大和入口防穿插:合并前试放候选碰撞体,检查边界适配后是否挤入无关地块;被占用时保留原块和得分,短暂延后重试。长大被挡时暂停,空间腾出后续播。入口被弹起的地块占住时,新块从其上方进入。连续碰撞用同一步的共同安全时间点限制深穿透;横竖屏变化采用统一缩放和平移,保留组合间距。吸附26px、磁吸45px和自由旋转规则不变。tests/overlap.test.cjs验证阻挡后的合并重试、增长暂停恢复、入口防重叠、高速接触、视口压缩及8组湖北45秒随机压力回归,并检查碎片仍持续运动。
难度确认更新(2026-10-17)
开启旅程、暂停重开和成绩页再玩统一先进入确认弹窗。弹窗使用草稿选项,确认后才创建新对局;取消不会丢失原局、成绩或视频。成绩卡、暂停说明、完整游玩视频标题与分享文案均携带实际对局的吸附难度和弹性。tests/difficulty.test.cjs覆盖三档位置/角度、真实合并、地图原点不影响宽松判定、非邻接过滤、防占用合并、远距力、12种吸附/弹性组合手动释放和湖北压力场景。
本次页面验证通过CUA在独立浏览器页操作并保存于本地artifacts:全国/省域开局确认、取消草稿、暂停重开取消保留、结果页再玩确认、三档实际生效,以及手机与横屏弹窗。npm test包含物理回归;此前tests/*.mjs中的定时界面场景属于旧流程,不作为当前手动模式的验收结果。平台模拟器、Android/iOS与Chrome61实际兼容性仍需同包验证。
留洞补位、回正开关与开局动效(2026-10-17)
轻松拼的位置/牵引范围从68/120px增强为84/148px,相对角度从约49°放宽到57°;刚刚好从42/78px增强为52/98px、约32°放宽到38°。两档牵引力及助推速度上限分别提高25%和12%;严丝合缝保持26/45px、约16°与此前牵引强度。
留洞补位按源地图轮廓的栅格补集洪泛判断封闭区域,包含源内环中的内嵌地区,避免把开口海湾或外侧省份当成缺口。合并组合会显示缺失轮廓;已落下的正确缺块靠近包围它的外圈、相对角度符合当前吸附难度且有真实陆地邻接时,允许跨越封口补入,不以全局进度放宽所有地块。靠近范围按距外圈轮廓的距离判断,为当前牵引范围的1.2倍加小幅轮廓半径补偿;目标仍经过第三方占位检查,被占用时保留原块和分数。480ms淡入和移动效果进入共享视频绘制器,得分与普通合并相同,成绩卡标记实际补位次数。
游戏底部暂停按钮旁提供“自动回正”滑动开关:每局默认关闭,可点击、左右滑动或用空格切换;不会触发游戏暂停或容器晃动。开启会唤醒所有现有组合,受控地朝地图正向转动,后续倾斜也会被纠正;关闭后不再施加回正控制。控制保留旋转速度上限和防穿透修正,拥挤时仍受实际碰撞限制,不强制瞬移角度。暂停冻结、横竖屏保留开关;新局重新关闭。成绩图标记本局是否用过回正辅助,视频按画面实际时刻显示开关状态。
开局、再玩或暂停重开的确认弹窗,根据发起按钮的真实位置和尺寸展开,时长380ms;设置弹窗保留普通进入方式。减少动态效果时不播放此展开动效或补位移动效果。
新增tests/pockets.test.cjs验证封闭/开口、内嵌城市、实际补位、距离/方向/邻接限制、第三块阻挡与真实湖北六邻省封口;tests/upright.test.cjs验证默认自由旋转、开启后多角度平滑回正、开关/暂停/缩放/结束边界,以及真实湖北连续晃动和反复切换的防穿透。独立预置测试页面位于本地忽略的artifacts/assist-fixture,只用于视觉复现,不进入正式ZIP或Git。
难度确认弹窗在外框展开结束后,仍保持内容区与操作区无独立入场动画,避免清理外框动画class时再次触发screen-in。全部地区出场后的提示统一改为“山河已到齐,继续拼合”,下一块预览与视频文案同步调整。
v2.1.0
首页标题旁新增红色v2.1.0,package.json与锁文件版本同步。成绩页移除独立视频生成按钮,将“发图文 / 发视频”合并为一个绿色主操作区,中间以斜线分隔;原进度行继续显示视频状态,不增加页面高度。右侧首次点击完成生成后自动进入视频发布编辑页,需用户确认才发布;再次点击复用已有视频。取消、生成失败、SDK缺失、发布取消和失败均保留原有降级及重试逻辑;生成期间离开原局或打开重开确认时,不自动拉起旧局发布页。
自动回正恢复老版轻量控制:转速按0.84阻尼衰减、按倾角0.022回拉,采用Git 465fee1的原有物理解算后控制:超出±0.16弧度(9°)直接限角、转速乘0.2,接近正向时归零,不改平移速度;正常碰撞和防穿透先完成;限角单独只读检查当前空间,受阻时暂缓,不撤销已发生的平移或牵连其他块。开启时唤醒一次,后续不在每帧清零休眠计数,静止块可正常休眠;晃动/碰撞会按原物理规则唤醒。关闭保留自然转动。tests/upright.test.cjs覆盖默认关闭、多角度回正、老版摇摆与转速上限、休眠计数/落地休眠/晃动唤醒、暂停/缩放、真实江西/新疆落地及湖北堆叠防穿透和持续运动。tests/publish.test.cjs用实际发布函数和模拟SDK验证一次点击生成并打开编辑页、取消、旧局隔离、无SDK降级和发布失败复用,未向平台发布真实笔记。
自动回正切换仅提示“自动回正 · 开启/关闭”,120ms淡入、短暂停留后180ms淡出,总时长800ms;连续切换重启动效,其他错误或发布提示保留原有阅读时间。系统减少动态效果时不播放淡入淡出。
本地战绩簿
地图导航行的“旅程战绩”进入完整战绩簿,包含全国1关、省内27关、县市区7关;按类别筛选,每页6关,列表内部滚动,首页主体与开局栏高度不变。每关展示历史最高分及三种吸附难度各自最高分,跨弹性档位取最高,零分已挑战与未挑战分开显示。每关每个吸附难度完整通关点亮一枚奖杯,共105枚;弹性、回正辅助不限,不重复发杯。主动结束和失败只保存分数。旧纪录没有通关标记,保留分数,不推断或补发旧奖杯。
新key shanhe-progress-v2将分数与通关状态放在同一JSON原子写入;首次读取合并原生/浏览器旧shanhe-records-v1与新进度,分数取高、奖杯取并集,保留旧key。加载期间的新成绩也并入,避免迟到数据覆盖。符合9.46版本及能力条件时使用官方Storage,否则兼容浏览器存储;读取失败时不覆盖未读缓存,保存失败提示当前页面暂存。本机缓存清理后可能丢失,不提供跨设备排行榜。tests/records.test.cjs覆盖35关105杯、迁移、隔离、失败与异步加载竞争。
自动回正性能修正(v2.2.0):旋转弹簧曾在每帧调用Sleeping.set(false),导致复杂地块无法累积休眠时间,拥挤堆叠持续参与碰撞;现已回到老版阻尼回拉和小幅摇摆。开关、短提示、视频/成绩辅助标记保持原样。Node物理对比与浏览器仅用于开发定位,不代表真机性能验收。
地级行政区下辖县区 GIS 素材
运行 python3 scripts/fetch-city-county-gis.py --snapshot 2026-10-18 获取并校验现有333个地级行政区对应的素材,输出到忽略的 data/city-county-gis/2026-10-18/,不改写正式 src/maps.js。支持 --only 330100,441900 小范围核验;同一快照目录内可复用已校验下载,更新快照使用新的日期目录。
2026-10-18重新获取结果为329组县区细分、2728块,另有东莞/中山/儋州/嘉峪关4份整市轮廓;这些城市的DataV整市属性为childrenNum=0、区县子级接口404,不能伪造县区,镇街边界需另行获取。原始GeoJSON保存于raw/及unsplit/,每市简化拼图坐标保存于levels/,manifest.json记录父级、名称代码、源URL、原始及简化文件SHA256、全部外环/内环数量与例外。坐标基准未由源接口声明,原始坐标原样保留;levels/使用游戏平面坐标,不作为经纬度GIS数据。
素材ZIP为 artifacts/city-county-gis-20261018.zip,包含664个文件、约8.6MiB,与可上传小工具ZIP分开。来源4389个外环、3个内环全部保留,父子关系、重复ID、坐标有限值、闭环/非零面积及ZIP CRC和逐文件字节一致性检查通过;这不代替权威最新区划、拓扑和关卡可玩性核验。该ZIP是原始/预处理素材,不是小工具游戏包;正式游戏接入情况见下一节。原始素材、简化预备数据、ZIP与报告均由现有.gitignore排除。
地级市县区关卡接入
已开放329个地级行政区的县区拼图(2728块),与原35关合计364关。进入省域地图后,点地级市进入县区准备页,也可直接从省域地图开启原省域拼图;县区页返回省域,再返回全国,导航栏和地图/开局栏高度保持不变。东莞、中山、儋州、嘉峪关的地图点击会说明没有县区子级,不虚构镇街边界。省直辖县级地区和已有直辖市/港澳台细分关卡保持原层级。县区密集地名采用避让与引线标注。
新增src/county-index.js仅保存目录和块数;src/atlas.js通过经典script按需加载包内county/下的静态文件,不调用网页网络API,不引用外部资源。源文件仍为329份单关素材,构建时按同省每两关合成173个脚本,发行目录同步重映射加载路径。首次进入不解析329份图形;同文件并发合并请求,最多缓存最近3个县区关卡,失败可重试,快速切换/返回使旧请求失效。成绩图、视频、暂停与重开使用当前城市关卡及省区名。战绩簿收录全部364关,按省区筛选和原分类分页,最多1092枚通关奖杯,分数与奖杯使用现有独立关卡ID存储。
发布数据已做保持拓扑的简化和坐标精度选择,保留独立岛屿与内环;13处源多边形自交由GEOS修复,来源和修复后的外环/内环数量记录在research/county-map-index.json。8个含内环的地区预置三角碰撞片,额外1个会使动态凸分解达到递归上限的地区预置静态碰撞片;不通过把非凸边界改成凸包覆盖邻区。
329份src/county/*.js是运行/编译需要的地图素材,连同小型目录与构建核对用来源索引进入代码库;原始GeoJSON、完整素材ZIP、工具环境和报告仍忽略。npm run build直接使用保存好的素材,新增审计核对全部333个地级入口的329可玩+4例外覆盖、父级和名称、文件SHA256、简单边界、内环碰撞与关卡连通性,不需下载GIS或安装Shapely。
重新准备素材时可执行(仅准备步骤需要Shapely):
python3 -m venv .cache/gis-runtime
.cache/gis-runtime/bin/python -m pip install -r scripts/requirements-gis.txt
.cache/gis-runtime/bin/python scripts/prepare-county-levels.py --snapshot 2026-10-18
node scripts/triangulate-county-holes.mjs
npm run build
新增tests/county-levels.test.cjs覆盖329关全部图块物理生成及正确拼合通关、县区/省域纪录隔离、三级导航与异步竞争、并发加载/失败重试/缓存淘汰,及杭州/楚雄/内嵌地区的持续晃动物理回归。普通浏览器仅开发核验;平台模拟器、Android/iOS同ZIP、Chrome61和低端机性能仍需实际验证。
v2.2.0 回正与物理热点优化
回正开关保留465fee1的阻尼/回拉/约9度摇摆手感,正常碰撞及防穿透先完成,再单独尝试本块限角/近零归零。空间被邻块或容器挡住时暂缓限角,继续保留平移和转速;腾出空间后再限角。仅对正在运动的地块应用,开启只唤醒一次。关闭时保留自由旋转。
防穿透二分回放改用只读刚体变换和凸片SAT投影,与Matter的2px穿透阈值一致,不生成接触点、不在每次试探中改写整堆顶点;只在最后提交一次安全姿态。保守扫掠包围框覆盖整个平移/转动路径,可能相遇的组仍共享同一个安全时刻,远处自由运动的地块不陪同回放。静止接触结果按位置/角度/缩放/包围框缓存,几何变化立即重新检查;WeakMap不保留已移除图块。
吸附缓存不可变组成员的邻接边,三档难度计算复用该边,合并扫描每组只算一次姿态;非邻接对不再重复扫边、检查缺口。组变化或边表替换后重新核对。吸附/磁吸的距离、角度、强度、得分和补位规则不变。
新增tests/physics-hotpaths.test.cjs用1200个插值位置与真实Matter碰撞对照,验证只读回放、最终一次提交、远处块不回退、静止缓存失效和吸附三档结果一致。scripts/profile-physics.cjs可复现全国/湖北/晋城县区在回正开关和晃动下的12个Node物理场景,输出平均/p95耗时、方法耗时、几何变更数与碰撞次数。计时含插桩开销,不含渲染/视频,不作为手机帧率或平台兼容性验收。
回正锁堆修正:此前把强制限角和普通物理运动一起送入防穿透回放,转正位置被邻块占据时,平移也被整帧退回。现将可选回正姿态检查移到防穿透之后,使用只读凸片SAT核对本块目标与容器/邻块,不安全就保留当前位置/倾角,只通过后续物理转速逐渐回正。新增固定复现:倾斜长条旁有障碍,原控制把2px横移退回到约0.46px;修正后横移大于1.8px,阻挡期间不硬改角度,障碍移开后可正常回正。保留全部防穿透阈值,不通过允许重叠或冻结图块规避问题。
动画停止的诊断与保护
物理受阻和整个画面停止是两个问题,尚未复现用户成都19/20块时的整画面停止,不能用物理回放优化作为已解决整页卡死的证据。成都12组80秒随机物理对局均未抛出异常,不包含浏览器渲染/视频开销。
动画帧分阶段记录物理/绘制/视频/界面,board的data-loop-health每500ms写一次紧凑本地诊断(帧数、耗时、关卡、回正状态与异常栈),不联网、不读取个人存储。下一帧统一在finally中按游戏状态调度;物理/绘制/UI报错会保留本局并显示可重试暂停界面,不再无提示终止RAF链。可选视频采样报错时停用该局视频并释放资源,主游戏继续;无视频会话的结算不再一直显示整理中。tests/frame-loop.test.cjs注入四个阶段的故障验证对应行为。
v2.2.0 单次回正(当前行为)
已移除自动回正滑动开关、持续控制器及其每帧调用,替换为暂停旁的“回正一次”胶囊按钮。一次点击只快照处理现有活动组合,直接设置正向0度并清除该次旋转速度,保留平移速度;没有落块、暂停或结束时不可执行。回正后即可自由翻转,后续新块/合并组不继承辅助,没有队列、后台重试或常驻限角。
按用户要求,按钮不提前试算碰撞、不避让、不挪位置、不因邻块阻挡而跳过,直接把所有当前组合角度设为0。后续由正常物理引擎继续处理接触。按钮图标有短促回摆动效。每次有效点击记录uprightCount,暂停页、成绩图和视频显示“回正N次”;已有uprightUsed辅助标记继续兼容,分数/奖杯规则不变。
测试覆盖直接全量正向、保持位置/平移速度、不调用碰撞试算、新块与之后自由转动、无后台重试、贴地不挪动、暂停/结束/尺寸变化以及真实湖北/成都多轮晃动的物理推进。历史章节中的自动模式仅为先前版本记录,以本节和当前源码为准。