Skip to content

PPSSPW:一次真实主流程 ​

玩家选择自己持有的游戏镜像后,网页需要先确认浏览器能力、配套核心和存档状态,才能把文件交给模拟器。本指南沿本地文件启动说明这条交接,帮助维护者区分页面能打开、核心能启动和游戏能流畅运行。

难点是网页、配套核心与存储状态必须同时就绪:文件不配套、浏览器缺少能力或存储维护互斥时,需要拒绝或延后启动,防止留下半启动状态。

下面的 case 是依据源码分支整理的代表性输入与结果,不是本轮线上观测。

输入或状态动作与结果
选择本地镜像并启动能力检查、存档恢复和核心就绪后交给模拟器
缺少 WebGL2 或共享内存提示具体原因并拒绝启动
存储维护正在持有互斥租约保持可重试,不写入半启动状态
核心只有 JS 而缺少 Wasm/data配套检查拒绝,不能混用不同构建

Step 1: 网页按顺序接上运行时 ​

打开页面后,Angular 服务只启动一次运行时加载,并按顺序装入性能、存储、文件、联机、触控和核心入口脚本。页面外观与模拟器运行由不同层负责;只构建网页不足以产生配套的模拟器核心。

打开「游戏库与设置」后,直接点击带图标和文字的栏目切换内容;选中状态会同时更新标签与对应面板。窄横屏下栏目排列为可横向滑动的一行,为内容保留高度。键盘可用左右方向键及 Home/End 切换栏目,Tab 从当前栏目进入面板;动态设备等设置仍使用原生选择框。

对应实现(仓库文件:wasm-page/src/app/ppsspp-runtime.ts),定位 PpssppRuntime.bootstrap / start / loadScript。

栏目结构(仓库文件:wasm-page/src/app/app.html)与切换逻辑(仓库文件:wasm-page/public/ppsspp-runtime.js),定位 panelTabList / activatePanelTab / onPanelTabKeyDown。

Step 2: 用户启动前核对浏览器能力 ​

点击启动时先验证所选文件和画布,再探测 WebGL2、Wasm SIMD、跨源隔离和共享内存。缺少能力时显示具体失败原因并停止;存在条件时尝试恢复跨源隔离。页面成功加载不能替代这一检查。

对应实现(仓库文件:wasm-page/public/ppsspp-runtime.js),定位 start、probeWebGLFeaturesWithPref、reloadForCrossOriginIsolation。

Step 3: 锁定存储并选择配套核心 ​

通过能力检查后启动音频初始化,并取得游戏存储的共享租约,避免维护操作同时改写文件。拿不到租约会恢复可重试的启动状态;随后选择包含配套 JS、Wasm 和 data 的构建。启动计时器会报告等待超时,不把长期无响应当成功。

对应实现(仓库文件:wasm-page/public/ppsspp-runtime.js),定位 PpssppGameStorage.acquireShared、selectBuildDir、startupWatchdog。

Step 4: 恢复存档并按需挂载镜像 ​

运行时在核心预运行阶段恢复持久化配置与存档,再把所选文件挂到浏览器文件层。读取缓存有界,不要求先把整个镜像复制进模拟器内存。普通本地文件选择只用于本次会话;保存进游戏库是另外的显式动作,存档则另行同步。

游戏保存先写入核心使用的内存文件系统,运行时随后异步同步到浏览器的私有文件存储(OPFS)。持久化目录位于站点的 OPFS 根下的 ppsspp-web/persist,其后才是核心文件路径;只查看 OPFS 根下的 persist 会遗漏真实数据。

同一次运行中提示反复出现时,先核对当前提示的确认键和“是/否”选择,再检查文件写入。游戏自己的提示按游戏规则处理;PSP 工具对话框按游戏请求的 buttonSwap 选择 ○/× 确认与取消,不能只根据网页按钮位置或全局设置推定。屏幕手柄状态、逻辑手柄和运行时采样三层都收到输入,只能证明这段输入链路工作;还要结合实际画面变化和文件变化判断游戏是否接受。对话框淡入期间也可能忽略按键。

观察到的情况下一步检查
提示在“缺少数据”和“未创建数据”之间往返区分返回/取消、选择“否”和真正执行创建;仅有循环与空目录不能证明写入失败或内存不足
核心目录已出现存档,OPFS 暂无对应文件当前自动同步间隔为 30 秒;等待一次同步窗口后,结合最近成功同步时间和路径、长度比较结果
重新打开页面后存档丢失核对上次同步是否完成、站点来源是否相同,再检查恢复阶段;同一会话的保存成功不替代重新加载验收

对应实现(仓库文件:wasm-page/public/browser-files.js),定位 CACHE_BYTES;在运行时搜索 mountBrowserGame / loadPersistedFiles。

输入桥与存档同步实现(仓库文件:wasm-page/public/ppsspp-runtime.js),定位 getSDLGamepads / opfsRoot / opfsParent / persistFilesNow / startAutoPersist;工具对话框确认规则(仓库文件:deps/ppsspp-wasm/Core/Dialog/PSPDialog.cpp),定位 UpdateCommon / IsButtonPressed。只读排查应从 OPFS 根逐级以 create: false 获取目录,检查文件名和长度,不读取存档正文或主动创建目录。

屏幕手柄支持多指同时操作。按住方向键后可连续拖到相邻方向或斜向;跨区换向会立即释放这根手指原先的方向,不会清掉其他手指仍按住的键。模拟摇杆以每次按下的落点为零点,小幅抖动留在死区,继续拖动才逐渐增加输入,抬手回中。例如一根手指保持摇杆向右,另一根短按圆圈键,圆圈抬起不会中断摇杆。普通短按仍保留最短脉冲,避免核心漏读;页面失焦、隐藏或旋转时会清空输入,防止卡键。

手柄的按压视觉只改变键帽,触摸命中区域保持原位。显示手柄时隐藏画面状态角标,避免它们与肩键和摇杆叠在一起;收起手柄后恢复角标原有显示规则。操作状态由触控控制器(仓库文件:wasm-page/public/touch-controls.js)的 createState / mount / dpadButtons / stickAxes 维护,外观由游玩控件样式(仓库文件:wasm-page/src/player-controls.css)负责;样式反馈不代表游戏已经接受输入。

折叠屏切换尺寸、旋转屏幕、打开侧栏或缩放页面时,手柄要使用变化后的坐标。应用按浏览器的布局尺寸排布,不把捏合缩放后的可视宽度当作新的应用宽度;低高度横屏会重新排列肩键和摇杆,竖屏按当前按键与摇杆尺寸预留控制区。布局改变会取消旧手势,重新落指后测量当前控件位置。例如一直向右拖动摇杆时折叠屏幕,输入先回中,重新触摸后才按新位置计算,避免沿用折叠前的落点。

窗口又窄又矮时,底部独立控制区可能挤掉游戏画面。此时手柄改为覆盖画面的紧凑排布:肩键、方向与动作键、摇杆与系统键分行放置,保留画布面积和按键命中区域。这个排布会占用部分游戏视野;桌面浏览器中的几何与坐标点击检查不能替代真实折叠屏的可读性和手指操作验收。

画布尺寸跟踪(仓库文件:wasm-page/public/ppsspp-runtime.js)的 syncViewportSize / installCanvasResizeTracking 在画布大小或设备像素比变化后通知运行时重算尺寸,仅位置变化时通知触控控制器释放旧手势。这是网页与核心之间的尺寸通知边界,不能代替原生游戏触点和不同折叠设备的验收。

原生窗口尺寸分支收到变化后,先刷新当前 DPI,再把窗口尺寸换算成像素并更新显示边界;全屏状态切换同样执行这一顺序。否则显示边界可能使用旧比例,后续输入却使用新比例。尺寸事件携带无效宽高时不更新显示边界,DPI 读取遇到零尺寸时保留此前有效值。窗口尺寸实现(仓库文件:deps/ppsspp-wasm/SDL/SDLMain.cpp),定位 SDL_WINDOWEVENT_SIZE_CHANGED / UpdateScreenDPI / Native_UpdateScreenScale。这项原生修改需要重新构建配套核心才会进入播放器,仅重建网页不会替换它。

原生设置页按核心的有效 UI 逻辑宽度选择栏目布局。网页画布即使保持横向比例,宽度不足 720 时也改用顶部横向导航;游戏设置栏目同时显示图标和文字,放不下时可以横向滚动。例如在“系统设置”中收窄窗口,布局切换沿用当前栏目和内容滚动状态,并将所选标签滚入可见区域,随后仍允许手动浏览其他标签。这个阈值使用核心 UI 单位,不直接等同于设备物理像素或网页宽度。原生栏目布局(仓库文件:deps/ppsspp-wasm/UI/TabbedDialogScreen.cpp),定位 UseHorizontalTabs / resized / CreateViews;栏目容器(仓库文件:deps/ppsspp-wasm/Common/UI/TabHolder.cpp),定位 Layout / PersistData。

设置页正在过滤搜索结果时,缩屏仍保留搜索和清除过滤入口;没有过滤条件而搜索页因空间不足被移除时,当前栏目回退到第一个有效页面。菜单重建会重新绑定搜索控件,避免继续访问已销毁的视图。这些原生行为需要完整重建核心,单独更新网页样式不会生效。本轮适配针对设置分栏,画布仍保持原有比例;不能据此推定原生主菜单及全部弹窗都已适配窄竖屏。

Step 5: 通过受限预览入口提供运行条件 ​

打包脚本检查必需核心产物,再复制到网页产物目录;本地服务器仅提供该打包目录,并设置线程所需的 COOP/COEP 等响应头。移动设备需要受信任 HTTPS;桌面能启动不能推出手机的帧率或兼容性。

对应实现(仓库文件:server/serve-player.py),定位 PlayerHandler、root;打包脚本 scripts/package-player.py。

检查这条解释 ​

sh
npm --prefix wasm-page test
python -m unittest discover -s server/player-tests -v
# 已激活 Emscripten SDK 且 PATH 有 Node.js 时,单独验证原生窗口尺寸分支
python scripts/test-sdl-viewport.py
# 使用同一工具链检查网页与非网页的栏目模式策略
python scripts/test-tabbed-layout.py

运行时与触控测试会执行真实 public 脚本函数并用替身验证边界;浏览器布局检查可复核栏目切换、焦点和触摸命中区域。窗口尺寸脚本抽取真实 C++ 函数与分支编译为小型 Wasm,SDL 窗口数据由测试替身提供,且不属于 npm test。栏目模式脚本同样提取真实布局决策和尺寸回调,分别将网页与非网页策略分支编译为小型 Wasm;它不构建桌面原生程序、不绘制界面,也不验证栏目滚动或搜索状态。上述检查不替代新核心完整链接、手机触摸手感和游戏性能验收。具体执行结果见指南变更记录;完整核心构建仍按根 README 的 Emscripten/Linux 或 WSL 要求执行。

CNB 与 GitHub Actions 调用同一构建入口(仓库文件:scripts/ci/build.sh),在完整核心构建之前执行窗口尺寸和栏目模式回归;两端手动部署也复用同一产物。脚本中加入检查不表示云构建或真实部署已经运行。

理解这条交接后,用代码地图定位下一次修改;需要反查依据时查看源码调查与边界证据。

证据状态:除特别标注外,本页基于当前源码已确认。