RevivalWorld

✅ 可玩

重生的世界

revivalworld

🔑 fluffos / Mud@2026 更新 fdf581a 2026-09-12 源码 下载 ZIP 上游 MudRen/RevivalWorld

▶ 开始游玩 · Play Now

台湾出品的正体中文(BIG5)经营/建设类文字 MUD,源码自称 RWlib,基于 MudOS v22.2b14 打造。与本收藏中占多数的大陆简体武侠对战类 MUD 家族完全不同宗——世界观、指令、地图/城市/任务名称均为英文(例如 "Suphia and Benjamin" 任务链),游戏内容围绕占地建城、 组织政府、经营商店仓库、农业转工业转知识时代的生产链,以及一套简单的股票市场展开,而非门派武功对战。

English

A Taiwan-origin, Traditional Chinese (BIG5) mudlib self-titled 重生的世界 (RevivalWorld), built on MudOS v22.2b14 and its own RWlib codebase. Unlike the wuxia combat-focused Simplified-Chinese family that dominates most of this collection, RevivalWorld is an economic-simulation and city-building game with English-named content throughout (areas, cities, and quest chains such as a 'Suphia and Benjamin' storyline): players occupy land, found cities and governments, build houses, shops, and warehouses, and progress through an agricultural-then-industrial-then-knowledge-era production chain, trading the results on an in-game stock market. New characters start at the Wizard Temple and choose a city to settle in via the gocity system. Boot renders a full HTML world map, and a real intermud2 daemon exchanges startup/mudlist broadcasts with other MUDs on the public internet.

README

内容亮点

详见 NOTES.md(英文,含完整移植细节)。

在线试玩

https://mudlibs.fluffos.info/revivalworld/

管理员账号 / Admin account

警告:Mud@2026 是本地游玩用的公开默认密码。若要正式对外开放
主机,请先修改此密码(或删除该账号及存档中的授权项)。

本地运行

cd libs/revivalworld
~/src/fluffos/build-debug/src/driver config.fluffos

游戏端口:40238

注意:intermud2_d.lpc 在启动时会绑定一个真实 UDP 端口并与公网
上的其它 MUD 交换广播包,不建议高频率自动重启此档案;开机预载
会持续约 2-3 分钟(html_d.lpc 渲染世界地图耗时较久,属已知的
单次开机成本,详见 NOTES.md),属正常现象而非卡死。

NOTES · 移植与修复记录

revivalworld (重生的世界) -- porting notes

Source: git clone https://github.com/MudRen/RevivalWorld (commit 71dcacb26b9db4a0813543761e2c610a2d4c04df, cloned 2026-08-26). Confirmed via gh repo list MudRen and a quick ls libs/ scan not to be a duplicate of anything already in this collection -- it is a genuinely different codebase/lineage from every other lib here: Traditional Chinese (BIG5), English-named source (RWlib) and content, built against MudOS v22.2b14 (a bundled MudOS-v22.2b14-RW-Build-Win32.zip Windows build is ignored, per convention -- only the LPC source was converted and run through this project's own FluffOS build), and an economic-simulation theme (land/cities/government, farming -> manufacturing -> knowledge-era production chains, an in-game stock market) rather than the wuxia-combat theme most of this collection's libs share. Slug revivalworld, number 936, port 40238.

Raw archive: 1,498 files (~13MB), 813 .lpc files after conversion. Top-level layout: city cmds combat data doc include log obj quest std system wiz www zone -- the mudlib root is the repo root itself.

Pipeline

1. Cloned into libs/revivalworld/raw/RevivalWorld/ (git-hosted source, .git stripped before copying in). 2. scripts/convert_lib.sh libs/revivalworld/raw/RevivalWorld libs/revivalworld/work BIG5 -- confirmed BIG5 (not the corpus default GB18030) by decoding system/kernel/master.c both ways and eyeballing the header comment ("主宰物件" only came out readable under BIG5). already_utf8=124 converted=1310 lossy=1 skipped_binary=37; renamed 813 files to .lpc; fixed 347 literal .c" references; static->nosave touched 12 files (checked for both AGENTS.md 4.3 collision classes -- neither present). 3. Filename-level mojibake (separate from file *content*, which convert_lib.sh already handles): a full tree walk for non-UTF8 *names* found two classes, both fixed by hand: - Four weather-data files under system/daemons/etc/ had their BIG5-encoded season names (春/夏/秋/冬季) still raw in the filename (decoding attempts as BIG5/GBK on the exact bytes gave nonsense -- resolved by reading each file's own first line instead, which named its season correctly) -- renamed to weather_春季 etc. - doc/help/ and doc/rwlib/ each had one real file plus a spurious PaxHeader/ sibling directory -- artifacts of the original archive being packed from a GNU-tar extended-header tarball with non-ASCII member names, committed to git as literal mangled bytes. Decoded the real filenames from the PaxHeader entries' own stored path= metadata (BIG5-encoded, itself double-mangled through an intermediate Latin-1 hop) -- doc/help/注音 ("Zhuyin/Bopomofo table", matching its content) and doc/rwlib/目錄結構 ("directory structure"). The two PaxHeader/ directories themselves are pure tar metadata, never real mudlib content -- deleted.

Fixes applied (driver-incompatibility bugs)

The big one: creator.lpc's own #define-header generator was silently wiping itself

system/kernel/creator.lpc (on the preload list, so this runs on every real boot) generates include/{feature,inherit,daemon,condition}.h -- every #define WHATEVER_D "/system/daemons/whatever_d.lpc"-style macro this entire codebase uses, including the ones simul_efun.lpc itself needs just to *compile* -- by scanning the source tree and stripping each file's directory prefix and .lpc extension to build the macro name. Its own .c->.lpc-rename-era extension checks/strips were never widened past the old 2-character .c shape:

Net effect, confirmed by root-causing via temporary write_file() instrumentation (this driver doesn't run any given object's create() during a bare single-file lpcc compile-check *unless* that object is actually loaded as part of the boot/preload/dependency chain, which made this take real digging to isolate): every boot silently overwrote all four header files down to just their comment stub (confirmed against the archive's real shipped versions, which have full content -- 85 lines in daemon.h, not 14). Once one of the earlier compile attempts in this session hit that regenerate-to-empty path, it took down essentially everything downstream: simul_efun.lpc itself failed to compile (Undefined variable 'CITY_D'/'MAP_D'/etc.), which cascaded into the "Master object 發生無限錯誤迴圈" (master hit its own infinite-error-loop guard) message repeating for literally every single file in a full lpcc_check.sh batch sweep (805/823 failures on the first attempt, all the identical shared root cause per AGENTS.md 6.4). Fixed by widening every one of the 4 generators' checks to <4..<1> and strips to <5> (system/kernel/creator.lpc), matching the general .lpc-vs-.c extension-width class already documented in AGENTS.md 4.2 point 4 -- just previously undocumented as also hitting a *check* condition, not only strip widths.

The identical bug shape recurred independently in two more places, each found by re-running the batch sweep and tracing the new failure back:

The database/set/query/delete architecture (F_DBASE-equivalent)

This lib's original custom MudOS build provided set()/query()/ delete()/set_temp()/query_temp()/delete_temp()/addn()/ addn_temp() as real driver efuns (see the bundled doc/driver/modify/source_files/packages/database.c for the actual C source) operating via C-level reflection on the *calling* object's own database/temp_database mapping variable, walking a "/"-separated path up to 20 levels deep. None of that exists on stock FluffOS. Restored as:

ansi/arith_operator/chinese custom-package restoration

Also custom driver efuns from the same bundled C source (doc/driver/modify/source_files/packages/{ansi,arith_operator, chinese}.c), restored as simul_efuns in the new system/kernel/simul_efun/rw_compat.lpc (included first in simul_efun.lpc's file list, since this driver resolves a same-file bare call against whatever's already been compiled earlier in the same translation unit -- AGENTS.md 6.5):

Master applies / PACKAGE_UIDS

system/kernel/master.lpc had no uid concept anywhere (this lib predates PACKAGE_UIDS) but this driver's build has it on and hard-fails without get_root_uid()/get_bb_uid(); also creator_file() -- called unconditionally for every single object load -- was missing too, and its absence produces a uniquely catastrophic symptom: not a boot failure, but every subsequent compile attempt in a batch sweep hitting master's own "Master object 發生無限錯誤迴圈" guard (335,000+ duplicate trace lines on the very first lpcc_check.sh run). All three added as simple single-uid-world stubs.

Hardcoded 3-port login architecture

include/login.h hardcoded PPL_PORT 5000 / WIZ_PORT 5001 / CLIENT_PORT 5002 -- the original server ran 3 separate driver instances, one per port, and login_ob.lpc::logon() dispatches purely on query_ip_port(), returning 0 (no login daemon at all -- the connection just sits open with no banner, ever) for any port that doesn't match one of the three. Since this project's convention (like every other lib here) runs a single port for everything, with admin status granted per-account rather than per-connection-port, changed PPL_PORT to this lib's real port (40238) so the one real port routes through the normal player login flow. Known limitation: this makes WIZ_PORT's dedicated flow (wiz_login_d.lpc) permanently unreachable -- an existing wizard-level account connecting through the single real port gets an informational reminder ("您必須由連接埠 5001 上線才能擁有 巫師權限") but is not blocked from playing; SECURE_D's file-permission ACL is account-based (a wizards mapping keyed by id), independent of connection port, so real admin file-access permissions are unaffected -- only the separate "enter through the dedicated wizard welcome flow" experience is unreachable now. If this lib's assigned port ever changes (e.g. a collision with a concurrently-onboarded lib caught after the fact), include/login.h's PPL_PORT must be updated to match, alongside config.fluffos.

Missing runtime directories (AGENTS.md 7.11)

mkdir -p'd after being caught live during registration testing (none of these ship in a fresh git checkout):

Assorted one-liners

Confirmed dead / not fixed (genuinely unreachable, per standing policy)

Boot / registration verification

Not run this session

深度功能測試(round two, 2026-08-27)

Full AGENTS.md §10.7 pass: newbie-help read, one continuous live session (raw Python socket scripts against ~/src/fluffos/build-debug/src/driver on port 40238, native, not WASM), registration through a fresh non-wizard character (qinfeng/TestPass123, deleted before committing -- see cleanup note below), movement into the coordinate map, quit/debug.log grep/wall-clock gap/relogin, and a systematic grep sweep for all six standing cross-cutting patterns (§7.121/§8.3a/§7.122/ §7.123/§7.124/§7.126). Two real bugs found and fixed; the economy/ stock-market systems specifically (the task's stated priority for this lib) turned out to be unreachable live for a reason that is itself documented below, not a bug.

Bug 1 (new AGENTS.md §7.11 bullet): missing city/fallencity1/0/room/ directory crashed restore_all_data() on every boot, which cascaded into a permanent 5-minute cron crash loop

system/daemons/city_d_main.lpc::restore_all_data() (called from city_d.lpc::create(), on the preload list) does foreach(roomfile in get_dir(CITY_NUM_ROOM(city,num))) to load each city section's built "module rooms". city/fallencity1/0/room/ doesn't exist in the shipped archive (a fresh/never-built-on section has zero rooms, and git can't track an empty directory), so get_dir() returned 0 and the foreach threw Bad argument 2 to foreach Expected: array Got: 0 at city_d_main.lpc:2794, confirmed live via log/system/preload (載入 /system/daemons/city_d.lpc 時發生錯誤). Two compounding symptoms, both confirmed via log/catch: 1. system_d.lpc's distributed_preload() catches the load_object() failure, logs it, and just drops city_d from the preload list (no retry) -- but a LATER pass over the same list (the /system/daemons/ wildcard entry, which legitimately re-lists city_d.lpc alongside its own earlier "must load first" explicit listing, per system/kernel/etc/preload's own comment) found the already-partially-created city_d object via find_object() and treated that as success -- masking the failure from a casual glance at the preload log (the SECOND 載入 line for city_d.lpc has no error after it, even though the object never finished initializing). 2. The crash happened BEFORE restore_all_data()'s own tail call to assign_cities_num(), which is the ONLY place that populates sort_save_list (a mapping used by time_distributed_save(), a */5 * * * * * cron job, "每五分鐘城市資料分散儲存"). With sort_save_list left at its default 0 (not even an empty mapping), every single firing of that cron thereafter threw Value being indexed is zero. on sort_save_list[++number] at city_d_main.lpc:369 -- confirmed live in log/catch, a fresh entry every 5 minutes, forever, for the rest of the boot's uptime.

Fix: mkdir -p city/fallencity1/0/room/ plus a .gitkeep placeholder (git can't track an empty directory, so without a placeholder file this fix wouldn't survive a fresh git clone -- re-checked and found the SAME gap on three other directories already mkdir -p'd during this lib's original onboarding session, data/bug/, area/, www/map/: all three exist in the current local working tree but were never actually committed as tracked content, so a fresh checkout would hit the identical missing-directory crashes this session already fixed once. Added .gitkeep to all four.) Verified live: a full reboot after the fix showed a clean log/system/preload entry for city_d.lpc (no error line) and log/catch stayed completely empty through a full registration → movement → quit → relogin session (previously it reliably grew a new entry every 5 minutes from the cron alone). See AGENTS.md §7.11's new bullet for the general "get_dir() on a missing directory returns 0, not an empty array, and a crash mid-init-function can leave a LATER global permanently unset" pattern.

Bug 2 (new AGENTS.md §7.128): process_input() returning the input string (instead of a nonzero int) made the driver's own "unknown command" fallback fire after EVERY player command, forever

Confirmed live on a completely clean, crash-free session: look, score, i, help, and command wizard all produced their correct real output, immediately followed by a spurious extra line reading 什么? ("what?") on its own. Root-caused via config.fluffos's default fail message : 什么? directive (a grep for the literal text across the whole work/ tree found it nowhere in any .lpc file, only in this project's own generated driver config -- the first clue this was a driver-level mechanism, not a stray mudlib string) plus ~/src/fluffos/src/comm.cc's process_input() C function, which inspects the LPC-level process_input() apply's return value to decide whether to ALSO run the driver's OWN native add_action()-based command parser on the input: a returned string re-parses that exact string through the (empty, in this lib's case) native action table; a returned 0/falsy number falls through to parsing the ORIGINAL raw input instead; only a genuine non-zero int skips the native parser entirely. revivalworld builds a complete custom dispatch of its own (std/inherit/feature/living/usr/_input_usr.lpc_command_usr.lpc's evaluate_command(), invoked via evaluate() on a captured function pointer) and never once calls the real add_action() efun anywhere in the whole codebase (confirmed: the codebase defines its OWN, differently -signatured, same-named add_action(object, mapping) LOCAL method in _action_usr.lpc/_action_npc.lpc that shadows the efun everywhere -- every "add_action(...)" call site in the tree is calling this local method, not the driver's). But _input_usr.lpc::process_input(), after fully handling the command itself via process_command(), ended every one of its 6 return statements with return input; -- the single worst choice, since it explicitly asks the driver to re-parse the very string it just finished handling against a command table with zero real verbs on it, guaranteeing the driver's own fallback fires. Fix: changed all 6 return input; inside process_input() to return 1; (verified via comm.cc that this is the ONLY return shape that fully skips safe_parse_command() -- a bare return 0; does NOT work, since 0 is still a valid T_NUMBER svalue and the driver's own check is specifically ret->type != T_NUMBER || !ret->u.number). Verified: lpcc --batch PASS on the edited file, then a full live session (register → south/look/score/i/skill/quest/helpquit → wait → relogin → look) showed the trailing message gone from literally every reply, log/catch/log/run stayed empty throughout. See AGENTS.md §7.128 for the general pattern (any mudlib with its own full command dispatch that shadows/never-calls the real add_action() needs process_input() to return a genuine non-zero int on every already-handled path).

Economy/stock-market systems: confirmed unreachable live in this specific save snapshot, and why -- not a bug

This lib's shipped world has exactly one city, fallencity1, and its raw persisted save data (city/fallencity1/info) has "fallen":1 and "name":"廢棄都市" (literally "Abandoned City") baked in directly -- this is the ACTUAL captured state of the original live server at archival time (zero citizens, zero government, zero assets), not a runtime bug introduced by conversion or by the missing-directory fix above. cmds/std/ppl/occupy.lpc's "found a new city on the ruins of a fallen one" flow is the ONLY way to un-fall a city, and it costs OCCUPY_MONEY = $RW 100,000,000 (system/daemons/city_d_main.lpc), completely unreachable for a brand-new character who starts with literally zero currency and no citizenship (citizenship itself, and the starting-money/land-deed grant that comes with it, requires physically registering at a functioning CityHall -- which doesn't exist in a fallen city). This blocks the ENTIRE land/production-chain/stock-market loop from a fresh player with no external funding, through any normal in-game path, in this exact save snapshot.

I also confirmed, while investigating whether the seeded fluffos admin account could be used to bootstrap around this (e.g. via the wizard-only givememoney command), that wizard-level commands are completely unavailable to ANY account connecting through this lib's single consolidated port, including the seeded admin account -- a materially bigger consequence than this file's existing "Known limitation" note above (written during onboarding) currently states. Root cause, traced end-to-end: system/daemons/virtual_d.lpc's compile_object() hook decides WIZ_OB vs PPL_OB for a restoring player body via SECURE_D->is_wizard(id) && (call_stack(1)[3]==load_object(WIZ_LOGIN_D) || call_stack(1)[3]==load_object(LOGIN_D)) -- but a RETURNING player logging in through the ordinary port takes ppl_login_d.lpc's OLD_PLAYER branch, which calls the shared load_user() simul_efun directly; the immediate caller frame at that point is ppl_login_d itself, which is neither WIZ_LOGIN_D nor plain LOGIN_D, so the condition is never true and PPL_OB is always chosen regardless of SECURE_D admin status. Confirmed live and directly (not just by inference): fluffos's own save file header reads #/system/object/ppl_ob.lpc, and both givememoney $RW 100000000 and command wizard/an arbitrary wizard-only verb came back 沒有「X」這個指令 (command not found) -- wizardp(this_object()) never becomes true for this account through this port, because enable_wizard() only ever gets called from _command_wiz.lpc's enable_interactive(), which only a WIZ_OB inherits. SECURE_D's file-permission ACL (used for e.g. SECURE_D checks gating file edits) is genuinely unaffected, since that's a separate, account-keyed mapping -- but essentially every in-game wizard COMMAND (givememoney, and by the same mechanism every other cmds/std/wiz/*.lpc/cmds/std/adm/*.lpc file) is unusable through the single real port. This is a direct, if more severe than previously described, consequence of this lib's ALREADY-DECIDED onboarding-time port-consolidation tradeoff (include/login.h's hardcoded 3-port architecture collapsed onto one port) -- not a new/separate bug, and not something this pass changed, since re-opening that architecture decision is out of scope for a deep-test pass. Flagging for whoever next needs in-game wizard-command access on this lib: the reliable workaround this session used successfully is direct persisted-save-file edits (as onboarding already did for secure_d.lpc's wizards mapping), not givememoney/other in-game wizard commands.

Given the above, the economic loop was exercised as far as is reachable by an ordinary new character with the shipped starting resources: registration, score/i (both empty/zero as expected for a brand-new character with no city), movement into the fallencity1 coordinate map (confirmed working -- landed at (50,50), "荒地"/wasteland, saw the "廢棄都市"/"第一都市" map labels), skill (correctly reports "你目前沒 有學習任何技能"), quest (correctly reports "你尚未完成任何任務"), help topics (pager works, <数字>/B/Q navigation not separately exercised beyond confirming the pager itself renders and advances). Land purchase, build, production chains (grow/collect/order), enterprise/business ownership, and the stock market (std/module/room/ stock.lpc's list/buy/sell) were NOT reachable live and are UNVERIFIED beyond the source-level review already on record above (the onboarding session's port/database/simul_efun restoration work, all of which the stock-market code correctly builds on -- count()'s string-arithmetic money handling, to_int()-wrapped share-price-to-cost conversions, etc. -- but none of that was exercised through an actual live buy/sell transaction this session). If this lib's world state ever needs the economy live-tested end-to-end, the practical path is a direct persisted-save-file edit granting a test character enough $RW to occupy city, or restoring from an earlier, non-fallen snapshot of the same city if one exists upstream.

Six standing cross-cutting patterns (§7.121/§8.3a/§7.122/§7.123/§7.124/§7.126): checked, no live instances found

Persistence verified

A full quit (clean exit via the quit command, not a timeout/ disconnect) correctly showed this lib's own quit flavor text ("你本次共 連線了十三秒" + a poem + "你離開遊戲了") and a subsequent relogin restored the exact same map coordinate ((50,50), confirmed both via the live look output and directly via the raw save file's quit_place field, ({49,49,0,"fallencity1",0,0,...}) from an earlier net-dead-timeout disconnect in the same test run -- confirming both the clean-quit AND the abrupt-disconnect save paths persist location correctly). log/catch and log/run stayed empty across the entire session, both before and after the two fixes above (i.e. the process_input fix didn't need the missing-directory fix to be verified, and vice versa -- independently confirmed).

Cleanup and verification before commit

Test character qinfeng (created for this session) was fully removed before committing: data/user/q/qinfeng/ deleted, and its residual entries in system/kernel/data/password.o/password.o_backup (the crypt hash) and data/daemon/money.o (an empty moneydata["qinfeng"] entry, created merely by existing as a registered citizen-less character, never funded) removed by hand, restoring those two files byte-for-byte to their pre-session state. Only the three original seed accounts (clode, msr) plus the seeded fluffos/Mud@2026 admin account remain. lpcc --batch re-confirmed the single edited file (_input_usr.lpc) compiles clean. The grep -h '"port"' libs/*/meta.json | ... duplicate-port sanity check (per task instructions) printed nothing before committing.

wasm_status 审计(2026-09-01):修复两处 sockets 包缺失崩溃,验证为 playable

meta.jsonwasm_status 此前一直留空(NOTES.md 已注明"WASM 通道 未构建",此前只做过原生驱动验证)。本次批量审计(见 [[project_wasm_status_audit]])补上这一步。

首次用 scripts/wasm_client.js 起跑,driver 直接拒绝启动:

/system/kernel/simul_efun/socket.lpc:20:46: error: Undefined function socket_status
No error handler for error: *No program in object '/system/kernel/simul_efun'!
The simul_efun (/system/kernel/simul_efun) and master (/system/kernel/master) objects must be loadable.

与本收藏此前已修复的 dsII/dsIII/lima/nightmare4 同一类 bug(AGENTS.md §1.3(c)):dump_socket_status() 无条件调用只有 编译了 sockets 包的驱动才有的 socket_status(),WASM 构建默认不带 这个包,导致这不是运行时缺失而是编译期报错,进而拖垮整个 simul_efun 编译。修复(#ifdef __PACKAGE_SOCKETS__ 包一层,没有 这个包时退化成空字符串,与既有修复手法一致)。

修复后再跑,登入对象本身编译失败,每个连线在 Can not accept connection ... due to error in connect() 后被直接拒绝: std/inherit/feature/living/usr/_ident_usr.lpcget_ident() (被 system/object/login_ob.lpc 每次连线都无条件调用一次)整个 函数体依赖 socket_address()/socket_create()/socket_connect()/ socket_error()/socket_write()/socket_close()——这是 AGENTS.md §1.3(c) 目录里"ident/auth 端口 113 查询"那一类的具体实例(此前只在 huoying 见过)。修复:整个函数体和其余几个只服务于这个功能的 回调函数一起包进 #ifdef __PACKAGE_SOCKETS__,没有这个包时 get_ident() 直接返回 0(表示身份识别不可用,原有调用方已经会 正确处理这个返回值)。

cmds/std/adm/wscheck.lpccmds/std/guest/ident.lpc/userid.lpc (客人可用的手动 ident 查询指令)、system/daemons/{dict_d,ftp_d, http_client_d,im_d,intermud2_d,mud_d,realnews_d,smtp_d,socket_d, socket_ob,translate_d,whois_d}.lpc 等文件也用到 socket 相关 efun, 但都不在开机/登入这条关键路径上——它们是懒加载的指令文件或预载 精灵,编译失败只表现为开机预载列表里的一行"Failed"(预载完成后 驱动照常打印"啟動完畢,重新連線中..."),不影响真实登入,保持 原样不动,与本项目对同类"外围精灵编译失败"一贯的处理方式一致。

开机行为提醒:这个 lib 的预载会在跑完整个精灵列表后主动 断开当前连线并提示"啟動完畢,重新連線中...",需要在测试脚本上 加 --reconnect-on-disconnectwasm_client.js 现有参数)才能让 客户端跟着重连,否则会被误判为登入失败。已确认这不是 bug,是与 README 里记载的"开机预载持续 2-3 分钟"配套的既有设计。

两处修复后跑通一次完整会话:new 创角 → 英文 ID/名称/密码/信箱/ 性别全部走完 → "歡迎 WasmTest(Wasmtest) 進入重生的世界。" → 落地 巫師神殿 → 5 秒后自动进入游戏 → 城市公告栏/新手提示正常显示 → quit 干净退出("你離開遊戲了。"),全程无未捕获错误。wasm_status 设为 playable