@@ -192,3 +192,171 @@ further here). Test characters removed afterward; fluffos kept.
192192Reverted an incidental ` data/topten.o ` leaderboard diff caused by the
193193test registrations (pre-existing tracked file, not meant to churn from
194194QA runs).
195+
196+ ## 深度功能测试 / Deep functional test (AGENTS.md §10.7, 2026-07-24)
197+
198+ One continuous native session (` ~/src/fluffos/build-debug/src/driver ` ,
199+ port 40037), following the §10.7 checklist end to end: read `help
200+ intro` / ` help newbie` / ` help newbie-basic` / ` help newbie-shaolin1` first,
201+ registered a real Chinese name (` qftester ` / 秦风, password ` test1234 ` ,
202+ born 嘉兴/group ` 3 ` , talents 20/20/20/20, personality ` 0 ` ), landed at
203+ ` /d/jiaxing/yanyu ` ("烟雨楼"), ` look ` /` score ` /` i ` /` hp ` all correct.
204+ Explored on foot toward 牛家村 reading room ` .lpc ` source
205+ (` d/jiaxing/*.lpc ` ) to navigate since exit lists intermittently hide
206+ under the lib's night-time darkness gate. Used the wizard ` spgo ` /
207+ ` summon ` commands (via the seeded ` fluffos ` admin) purely to fast-travel
208+ the SAME test character to distant content (少林寺 练武场/广场) rather
209+ than trekking the whole map by foot — the actual sect-join, skill-learn,
210+ combat, and shop interactions below were all performed by the ordinary
211+ player character, not the admin.
212+
213+ ** Safe sparring** : the newbie guide's suggested targets (a village
214+ ` 小孩 ` /` 女孩 ` ) both declined ` fight ` — traced this to their ` attitude `
215+ being genuinely ` "friendly" ` in their own ` create() ` (`/d/village/npc/
216+ kid.lpc` , ` /d/village/npc/girl.lpc` ), which ` inherit/char/npc.lpc`'s
217+ ` accept_fight() ` is coded to always decline (`case "friendly": ...
218+ return 0;`) — working as designed, not a bug (a different, unrelated
219+ ` /d/jiaxing/npc/boy.lpc ` /` /d/village/npc/boy.lpc ` with `attitude:
220+ "peaceful"` momentarily looked like a mismatch until the actual live
221+ object path was confirmed via ` call obj->query(...) ` — red herring, not
222+ reused). Used the lib's own documented, code-confirmed-safe wooden-dummy
223+ mechanic instead (` d/shaolin/obj/mu-ren.lpc ` , ` accept_fight() ` checks
224+ ` me->query("damaged") ` /a ` random(fight_times)>=10 ` break-chance, exactly
225+ the §10.7-item-3 shape): ` fight mu ` at ` /d/shaolin/wuchang1 ` exchanged
226+ several rounds of blows with correct combat text, ` halt ` ended it
227+ cleanly, ` hp ` /` i ` afterward correct, zero ` debug.log ` errors.
228+
229+ ** Skill/sect join** : followed ` help newbie-shaolin1 ` 's documented path —
230+ ` apprentice biqiu ` (清为比丘 at ` /d/shaolin/guangchang1w ` ) immediately
231+ accepted as a 俗家弟子/少林派第四十一代弟子 (no manual `ask ... about
232+ 出家` / ` kneel` needed for this particular monk); ` score` showed 师父/称谓
233+ updated correctly; ` skills biqiu ` listed the master's own skill roster;
234+ ` xue biqiu dodge 1 ` (organic NPC-teacher path) advanced 基本躲闪 from
235+ unlearned to ` 1/0 ` , ` cha ` confirmed it. Both the sect-join and
236+ skill-learn steps worked correctly, no separate admin-command shortcut
237+ was tested (none is needed/expected in this lib — ` apprentice ` IS the
238+ join mechanism).
239+
240+ ** ` quit ` → debug.log → reconnect (§10.7 items 5, 8, 9)** : clean ` quit `
241+ produced no warning (confirms the pre-existing ` savequit() ` fix from the
242+ "Bonus fix" section above still holds) and left zero new ` debug.log `
243+ lines. Net-dead (dropped the connection without ` quit ` ) and reconnected
244+ PROMPTLY (a few seconds later): worked correctly, position/state intact.
245+ ** Net-dead and NOT reconnecting until the real timeout elapsed found a
246+ new, live-reproduced bug — see below.** Separately, clean ` quit `
247+ followed by a genuine ~ 75-real-second wall-clock gap, then a FULL fresh
248+ login (not a net-dead reconnect) via ` qftester ` /` test1234 ` : correctly
249+ restored sect membership, skill, and all ` score ` fields from the
250+ on-disk save file (` data/user/q/qftester.o ` — ` family ` mapping,
251+ ` skills ` /` learned ` mappings, ` dbase.qi ` /` jing ` etc. all intact).
252+
253+ ** Shop purchase (§10.7 item 6)** : character had no starting funds
254+ ("你没有存款"), so used the admin account to ` clone /clone/money/coin ` ,
255+ ` call coin->set_amount(500) ` , ` spgive qftester coin ` (funding only —
256+ the purchase itself was performed by the ordinary player character).
257+ ` list ` at 烟雨楼's 店小二 showed the menu correctly; ` buy jitui ` (30
258+ 文) succeeded, correctly made change into higher denominations (500
259+ 铜钱 → 1 烤鸡腿 + 4 两白银 + 70 文铜钱); ` eat jitui ` consumed it. No
260+ errors.
261+
262+ ** Not verified live** : progression to a real death/respawn cycle — the
263+ session budget went to the net-dead bug investigation below instead;
264+ code review only (` d/city/ghost*.lpc ` /鬼门关 revival flow referenced by
265+ ` help newbie-basic ` 's〖死亡〗section was read but not exercised). A
266+ sect promotion (拜达摩/升辈) is a multi-hour undertaking per the
267+ newbie guide's own admission and was not attempted.
268+
269+ ### Bug found and fixed: ` tell_room() ` 2-arg wrapper crashes the net-dead force-quit handler — matches AGENTS.md §7.12 exactly (severity-escalated instance)
270+
271+ ` adm/simul_efun/message.lpc ` 's ` tell_room() ` wrapper:
272+
273+ ``` lpc
274+ varargs void tell_room(mixed ob, string str, object *exclude) {
275+ if (ob) message("tell_room", str, ob, exclude); // BEFORE
276+ }
277+ ```
278+
279+ Every 2-argument call site leaves ` exclude ` as LPC's default ` int 0 `
280+ (not an empty array), and the real ` message() ` efun's 4th argument
281+ rejects a bare ` 0 ` . ` clone/user/user.lpc ` 's ` user_dump() ` — the
282+ ` NET_DEAD_TIMEOUT ` -driven (900s / 15 real minutes, ` include/user.h ` )
283+ force-quit handler that runs when a net-dead player never reconnects —
284+ calls ` tell_room(environment(), ..., "\n") ` with only 2 args at its very
285+ first line (the DUMP_NET_DEAD case). This is byte-for-byte the same
286+ lib/function/case/timeout shape AGENTS.md §7.12 documents as
287+ "severity-escalated" from ` dtsl ` 's deep functional test: the crash
288+ aborts ` user_dump() ` at that exact statement, so the two lines after it
289+ (` "/cmds/usr/exit"->savequit(this_object()) ` ) never run — ** silently
290+ disabling the entire net-dead force-quit safety net** : a player who
291+ net-deads and never manually reconnects would stay alive in server
292+ memory forever (never actually saved-and-removed via this path) until a
293+ driver restart.
294+
295+ ** Live-reproduced** (not just pattern-matched): temporarily lowered
296+ ` NET_DEAD_TIMEOUT ` from 900 to 12 (` include/user.h ` ), ran the wizard
297+ ` update /clone/user/user ` command to recompile it, connected as
298+ ` qftester ` , dropped the connection without ` quit ` , waited past the
299+ shortened timeout:
300+
301+ ```
302+ 执行时段错误:*Bad argument 4 to EFUN message()
303+ Expected: object, array, Got: int(0).
304+ 程式:/adm/single/simul_efun.lpc 第 77 行
305+ 呼叫来自:/clone/user/user.lpc 的 user_dump() 第 75 行,物件: /clone/user/user#105 ("秦风")
306+ 呼叫来自:/adm/single/simul_efun.lpc 的 tell_room() 第 77 行
307+ ```
308+
309+ ** Fixed** (the standard AGENTS.md §7.12 wrapper fix, protects every
310+ 2-arg call site in the lib at once, not just this one):
311+
312+ ``` lpc
313+ varargs void tell_room(mixed ob, string str, object *exclude) {
314+ if (ob) message("tell_room", str, ob, exclude || ({})); // AFTER
315+ }
316+ ```
317+
318+ Re-verified with the same shortened-timeout methodology: no crash,
319+ ` debug.log ` clean, and ` who ` afterward showed only the admin online —
320+ ` qftester ` was correctly force-quit-and-saved this time (confirms the
321+ two previously-dead lines, including ` savequit() ` , now execute).
322+ Restored ` NET_DEAD_TIMEOUT ` to 900 and did a full clean driver restart
323+ before finishing (the test-only timeout value was never left in place).
324+
325+ ** Note for the project** : two other confirmed "Century/` adm-single ` "
326+ lineage siblings (AGENTS.md §11) — ` shiji ` and ` xianjianchuanqi ` — still
327+ carry the exact same unfixed 2-arg ` tell_room() ` wrapper in their own
328+ ` adm/simul_efun/message.lpc ` (checked via grep during this pass, not
329+ fixed — out of scope for this task, which is ` xiakexing100 ` -only).
330+ ` shujian2008 ` , ` xiakexinzhuan2 ` , and ` zhonghua2 ` (same family) already
331+ carry the fix from earlier passes. Worth a follow-up pass on ` shiji ` /
332+ ` xianjianchuanqi ` specifically for this bug given ` xianjianchuanqi ` is
333+ also the lib AGENTS.md §10.8 documents hitting an unrelated driver-fatal
334+ crash ~ 25 minutes into its own net-dead soak — the two are different
335+ bugs but both live in the same net-dead code path family.
336+
337+ ### Observation, not fixed (design ambiguity, see §10.7's scope note): two independent day/night clocks can visibly contradict each other
338+
339+ ` cmds/std/look.lpc ` 's exit-listing darkness gate
340+ (` 天色太黑了,你看不清明显的出路 ` ) keys off ` NATURE_D->game_time() ` ,
341+ which derives an in-game shichen (時辰) hour from a heavily-compressed
342+ calendar clock (` adm/daemons/natured.lpc ` : `#define TIME_TICK
343+ ((time()%(60* 24* 3600))* 365)` — 60 mud-years pass every 60 real days,
344+ i.e. roughly 6 mud-hours per real minute, confirmed live: the session's
345+ in-game date advanced across several 时辰 within a couple of real
346+ minutes). The SEPARATE sun-position flavor line
347+ (` NATURE_D->outdoor_room_description() ` ) keys off ` current_day_phase ` ,
348+ a ` nosave ` variable advanced by its own independent real-time
349+ ` call_out("update_day_phase", ...) ` cycle. These two clocks are not
350+ synchronized and were observed live disagreeing outright in the same
351+ room printout — "一轮火红的夕阳正徘徊在西方的地平线上"(sunset flavor
352+ text) immediately followed by "天色太黑了,你看不清明显的出路"
353+ (darkness gate, only fires for 亥/子/丑/寅时) moments apart, and at one
354+ point "太阳正高挂在西方的天空中"(afternoon flavor text) paired with the
355+ SAME darkness message. Purely cosmetic/exit-listing (movement in a
356+ known direction still works even when the list is hidden — confirmed by
357+ navigating through several "dark" rooms via directions read from
358+ source), and both clocks' formulas are pure LPC business logic with no
359+ encoding/efun/grammar involvement, so this is very plausibly authentic
360+ original-game behavior rather than anything this project's conversion
361+ introduced. Documented per §10.7's scope note rather than "fixed" by
362+ guessing which clock should govern the gate.
0 commit comments