Entry 18
Event scripts: format, all 167 opcodes, and the story's control flow
This entry revisits The desktop text: mail, board and news, in English and parody.
Documented in The event interpreter, Event scripts, How the game is put together, The desktop text - mail, board, news.
Filestools/evscript.pytools/test_evscript.pydocs/engine/events.md
The story runs on event scripts: arrays of short compiled into main as C
data, interpreted by ccEvent::Execute (INF SLUS_202.67:0x001a8d20,
38,708 bytes, evmng.cpp lines 2641-6972) and gated by ccEvent::CheckOpen
(0x001a7400). All 167 opcodes, all 40 open conditions and all 12
precondition tags are decoded, tools/evscript.py disassembles every script,
and every one of the 192 scripts walks to its terminator with no desync. The
reference is the events page.
The format
There is no header and there are no jumps: every opcode has a fixed length, and control flow is entirely by guarded blocks.
open conditions ... 0 the event is live when all of them hold
block, up to 62 of them:
(-2 tag operands)* persistent preconditions (SetCurrentOpen)
conditions ... 0 this block runs when all of them hold
instructions ... 0 the block's body
-1 end of the event
eventSub (0x001b5ef0) and ccEventFlagSet (0x001b6160) hold the walk
loop. Each event has a 64-bit word in saveData.eventFlag (+0x54f8, 16):
a block that finishes sets its own bit - unless it ran opcode 4,
repeatable - and is skipped from then on; bit 63 closes the event, bit 62
marks it done, and event_done tests bit 62. ccStartThEvent promotes 63 to
62 for events 0-449 when the event task restarts, so the next story event
opens after the next restart - a mode change, for example (inferred from the
code). The interpreter's lv argument decides what runs: 0 walks a block
without effect, 2 plays it, and 1 - used by ccEventFlagSet to fast-forward
earlier volumes - applies only the bookkeeping. Of 166 real opcodes, 120 run
only when playing, 35 also at lv 1, 10 do different things at each, and one
(virus_core) only at 1.
Event N's script is eventTbl[N / 50][N % 50].tbl (eventTbl,
0x00317e30, {short *tbl; char *str}, str a label like
"MG0001 OPENING"): groups M1, S1, M2, S2, M3, S3, M4, S4, then ML for
400-499. 192 events map one-to-one onto 192 arrays, 117,366 bytes. Messages
come from evMsgTbl[N / 50][N % 50] - evMsgTblp in Parody Mode - and voice
from the event voice tables of 14. The main-story message tables for
volumes 2-4 are null on this disc: 933 of the 1,512 message opcodes in
volumes 2-4 have no text, while all 605 in volume 1 do. Like the mail, the
bosses and the story areas (9, 11, 12), the scripts for the later
volumes are here; their dialogue is not.
Parody Mode is a different script
9 read the parody tables as the Japanese original of the same text - the mail and news entries it compared agreed line for line. The event messages do not. In event 2, where the English has Orca greet Kite and Kite hesitate, the parody table has Kite greet Orca by his real-world name and Orca yell at him for it. Parody Mode is a rewritten comedy script. It shipped on the US disc in Japanese, untranslated, and at least the event dialogue differs in content. 9's statement that the parody entries it read were ordinary translations stands for those mail entries; its wider reading of what Parody Mode is does not.
How it was verified
Statically, every path from every opcode's case back to the loop head was
followed through the nested jump tables, tracking every register and stack
slot derived from the script pointer: each case advances it by exactly one
constant and none stores a computed pointer, which is what rules out
variable-length opcodes and jumps. The DWARF locals of Execute and
CheckOpen, in source-line order, match the cases one to one; that is where
the operand struct names come from.
Dynamically, in tools/eemu.py (9): the real Execute, CheckOpen and
SetCurrentOpen, run at lv 0 on a single opcode, give the table's length
for all 167 opcodes, 40 conditions and 12 tags; a Python copy of the walk
loop calling the real functions stops at exactly the parser's block ends for
all 192 scripts (evscript.py check: 0 mismatches, and it does catch a
deliberately broken table entry); 29 bookkeeping opcodes run at lv 1 change
saveData exactly as described; and conditions 1, 10, 11 and 39 give the
documented comparisons. Opcodes 2 and 3 and the block-bit epilogue use
dsllv, which eemu does not interpret, and were not run.
Survey: 1,935 blocks, 13,515 instructions, 2,059 conditions, 1,918
preconditions. 145 of 167 opcodes are used; the commonest are message
(1,993), wait (1,596), camz_set (829), menu_ban (571), pc_face (547).
tools/test_evscript.py has 7 tests, including event 1's disassembly and the
full game-code check.
The opening, as the script tells it
Event 1, MG0001 OPENING, opens when event 0 is done and the game is in mode
2. Its first block, at phase 0, sets 30 frames per second, loads gcmn, plays
cutscene stream 2, returns to 60 frames per second, plays stream 106, loads
the desktop overlay, shows two messages (the registration done, and Kite
thinking of Yasuhiko waiting), runs name entry,
intercepts every desktop operation except one, and delivers mails 4, 5 and
320, seven bulletin board posts and five news items. Until mails 4 and 5 have
been read, any other operation answers with Kite's line that the mail comes
first - which
suggests operation 1 is the mailer. Then the board fills with 57 posts and the
event closes.
the numbering of cutscene streams, markers, menus, sound commands, player operations and NPC/PC action codes; the mail and BBS state values; the camera angle units; whether the ML events (400-455), which wait on later-volume events, can open in Infection at all; how far the parody script diverges beyond the lines compared here.