-
Notifications
You must be signed in to change notification settings - Fork 0
Event Script Opcodes
This note decodes the bytecode dispatcher used by the event.dat script section.
Derived from:
- dispatch loop at
0x172CA/0x1750C - opcode jump table at
0x17494 - handler stubs at
0x172D8..0x17484
- Bytecode opcodes are in range
0x00..0x32. -
0xFFterminates a script block. - Invalid opcodes branch to
0x1748Cand set abort flagA4-$8616. - After each opcode, control returns to
0x1750Cto fetch next byte.
The following were re-confirmed directly from mm2.capstone.asm:
-
Tile-event scanner
0x1754A: triplet table is 3-byte groups(pos, event, cond)read from offset 0, terminated by the first00 00 00group. String-table offset =(terminator+3) + str_rel_word; script PC start =terminator+5. This matchesdecode_event.decode_locationexactly. -
Position byte =
(row<<4)|coli.e.(y<<4)|x(0x17608-0x17618), confirming the(y,x)trigger order. -
Cond byte is AND-masked with the direction/context byte and the event is
skipped when the result is zero (
0x17682and.b+beq). -
Event id selects a record in one
0xFF-delimited pool (skip loop at0x17262). The pool mixes opcode scripts and plain-text records. -
Dispatch
0x172CA-> table0x17494; valid opcode range0x00..0x32(cmp.l #$33at0x174FA). -
argcfix:OP_03reads 1 byte (was previously logged as 0). -
Special records: locations 63, 65, 68 contain no
00 00 00terminator and are not processed by the tile-event scanner (castle / Hall-of-Spells style data); the decoder now flags these as unreliable.
argc is inferred from read_u8/read_u16/read_u24 helper usage in each handler.
| Op | Handler | argc | Notes |
|---|---|---|---|
00 |
0x1748C |
0 | invalid/abort |
01 |
0x15924 |
1 | show text; u8 string index (via 0x15884->0x155BE) |
02 |
0x15942 |
1 | show text block; u8 string index (stub also pushes const 0x13) |
03 |
0x159CE |
1 | show text; calls 0x15942 (const 0x11) which reads u8 string index |
04 |
0x159F4 |
1 | show text above door; u8 string index |
05 |
0x15A46 |
1 | show text popup A; u8 string index |
06 |
0x15AEE |
1 | show text popup B; u8 string index |
07 |
0x15CE6 |
0 | wait-loop until SPACE key, then continue |
08 |
0x15D26 |
0 | wait for key (sets cursor mode 0xFD, calls wait helper 0x15CE6) |
09 |
0x15D3C |
0 | y/n prompt, writes result into condition flag |
0A |
0x15D9A |
0 | y/n prompt variant (mode 0xFD, calls 0x15D3C) |
0B |
0x15DB0 |
2 | open service/title window (u8 string via 0x15756, u8 position) |
0C |
0x15E12 |
2 | map transition to (u8 dest, u8)
|
0D |
0x15EC4 |
1 |
u8 -> engine thunk -$7E42 (generic engine call) |
0E |
0x160C2 |
1 | selector dispatch (see selector table below) |
0F |
0x162A6 |
0 |
end/return script (sets stop flag, calls cleanup 0x171AC) |
10 |
0x162B8 |
var | IF(cond) skip N token-stream entries |
11 |
0x162DC |
var | IF(!cond) skip N token-stream entries |
12 |
0x16300 |
12 | encounter/combat setup: 10-byte block + 2 bytes (dispatch pushes 0) |
13 |
0x16386 |
10 | encounter setup variant (calls 0x16300 with flag=1, 10-byte block) |
14 |
0x16398 |
0 | clear current tile event high-bit / clear runtime event flag |
15 |
0x16426 |
3 | apply state to party members (u8 count, u8, u8; 4th byte only when flag=1) |
16 |
0x16520 |
2 | scan up to 6 monster slots; set cond if field +0x3A/+0x28 matches arg2 |
17 |
0x165A4 |
2 | load condition byte from resolved variable pointer |
18 |
0x165C6 |
4 |
OP_15 handler with flag=1 (u8 count, set, and, or) -> masked party apply |
19 |
0x165D8 |
4 | add entity to party/roster (writes fields +0x3A/+0x40/+0x46) |
1A |
0x166F8 |
2 | store u8 into resolved variable pointer |
1B |
0x16724 |
1 | clear cond unless cond >= u8 (threshold gate) |
1C |
0x16742 |
1 | engine query -$7BB4(1, u8) -> cond |
1D |
0x16762 |
1 | engine call indexed by u8*7+1 (-$7E84) |
1E |
0x16780 |
1 | timed wait/delay loop for u8 iterations (-$7BC0/-$7BD2) |
1F |
0x1690E |
6 | apply effect to party members (selector + values via 0x167B0) |
20 |
0x16A22 |
6 | party-effect variant (calls 0x1690E with mode=1) |
21 |
0x16A34 |
3 | set tile data at (y,x) (arg1 nibbles) into arrays -$55BA/-$54BA
|
22 |
0x16A9E |
2 | set condition if current era (-$79B5, low byte of era word) is in [lo, hi] — era/time-range gate |
23 |
0x16ADA |
2 | reads day-of-year (-$79DE[era]) and range-checks it (calendar/day gate) |
24 |
0x16B54 |
2 | check u16 gold amount -> condition flag (strong evidence) |
25 |
0x16B82 |
2 | check non-gold 16-bit code -> condition flag (hi,lo bytes) |
26 |
0x16BC0 |
0 | prompt to select a party member (ESC 0x1B ends script) |
27 |
0x16BC0 |
0 | select-party-member variant (input mode 1) |
28 |
0x16C86 |
2 | consume item id (arg1) from party inventory -> condition flag |
29 |
0x16D08 |
0 | force abort/exit flag |
2A |
0x16D16 |
14 | set treasure/reward block: u24 gold/exp, u16 gems, 3x(id,?,?)
|
2B |
0x16D74 |
var |
u8 + token-skip walk |
2C |
0x16D98 |
1 | add u8 to state var -$79B8, flag redraw |
2D |
0x16DBA |
2 | check party-member attribute (arg1 bits select fields +0xC/+0xE/+0xF) -> cond |
2E |
0x16F50 |
2 | set attribute bit (or arg2) on matching party class -> field +0x51
|
2F |
0x16FEA |
0 | clear/space-fill input buffer -$5C50
|
30 |
0x17034 |
10 |
answer/password check: 10 bytes vs player input; char = 0x11A - byte (0xFA=space) |
31 |
0x170BC |
3 | iterate target party members and call engine op |
32 |
0x17190 |
1 | load condition from script variable id |
Opcodes 0x10, 0x11, and 0x2B call token skip helper 0x157FC.
That helper:
- Reads a count byte.
- Repeats
counttimes:- reads current token byte from script stream,
- looks up token length delta in table at
A4-$6CC8, - advances script pointer by that delta.
So these opcodes are deterministic but variable-length and need token table support for full static disassembly.
-
0x22(0x16A9E) checks current event id (A4-$864B) against script range args and writes boolean to condition flagA4-$86AF. -
0x10/0x11branch via token-skip helper0x157FC, gated byA4-$86AF. -
0x14clears current tile's visited/event bit inA4-$AB46and clears runtime flag byteA4-$AA2Abit 7. -
0x29sets abort flagA4-$8616 = 1. -
0x32copies a variable byte (via engine thunk-0x7F2C) into condition flagA4-$86AF.
OP_0E reads one selector byte and runs a chained-subtraction switch:
-
Explicit cases call dedicated engine thunks:
-
1tavern/food,2..8other town buildings (each a distinct-$7Dxxthunk), -
100 (0x64),126..131 (0x7E..0x83),201..207 (0xC9..0xCF),226 (0xE2),253 (0xFD)are further specials (travel/portal/quest handlers).
-
-
Default (any other selector) ->
0x15EDC, which bins the selector into a service category using engine range-check-$7E9Cand calls town handler-$7DFA:selector range category byte adjusted index base 0x09..0x100x3C-80x11..0x370x3D-0x100x38..0x4B0x3E-0x370x4C..0x540x3F-0x4B0x56..0x5B0x40-0x550x5C..0x5E0x41-0x5B0x65..0x690x42-0x640x6A..0x7C0x43-0x690x97..0x980x44-0x960xE3..0xF30x45-0xE20xF4..0xFB0x46-0xF3
Gameplay-observed selector meanings (shops/temple/training/guilds) live in the
SELECTOR_NAMES map in tools/decode_event.py.
OP_30 stores a 10-byte expected answer inline, encoded as byte = 0x11A - ascii
(0xFA = trailing space). Decoded answers found in event.dat:
-
"MEENU","KEYS"(matching user-reported answer words), and numeric combination codes"46","23","64","32".
After the argc corrections (OP_12=12, OP_13=10, OP_15=3, OP_18=4,
OP_30=10), a linear walk of every real script segment now consumes each
segment exactly: 0 overruns, 0 underruns (excluding token-skip and text
records). Previously there were 35 underruns from the wrong arg counts.
-
Shop/sign tiles (
Carriage Inn,Drewnhald Ironworks,Boar's Tongue Tavern, etc.):- Behavior: text only, retriggers when stepping onto tile again.
- No immediate movement/state side effect observed.
-
Arena gate:
- Without ticket: message shown (
"Sorry, but you must have a ticket to compete in these games."). - With ticket: immediate combat starts.
- Post-fight: returned to same gate area.
- Retrigger rule: step off and back onto trigger tile.
- Ticket is consumed per fight.
- Script chain in this location uses item-check style ops (
0x24/0x25) around gate logic, consistent with ticket gating.
- Without ticket: message shown (
-
Olympic Trial:
- Current observed behavior: text only (
"Hurl the spear..."), no immediate combat/transition.
- Current observed behavior: text only (
- There are 4 ticket items in
items.dat(color-coded):- green
- yellow
- red
- black
- There are 4 key items (town-purchased), used to free bishops in castles.
- Arena world model:
- Arenas exist in Middlegate, Atlantium, Sandsobar.
- Tundra has no tickets.
- Volcania uses the red ticket line.
-
OP_24is strongly indicated as a gold check opcode (not item-id):- observed args include
10,25,50,200,500, matching common payment gates.
- observed args include
-
OP_25is a non-gold 16-bit predicate path (small observed codes0/1/2so far). - Arena gate blocks likely follow:
- predicate (
OP_24/OP_25) -> condition flag - conditional skip (
OP_10/OP_11) - combat/arena transition call
- ticket consumption side effect
- predicate (
- Castle bishop gates should provide additional traces for key-specific checks using the same opcode family.
Extracted OP_24 arguments across locations:
-
0x000A(10) — Middlegate flow -
0x0019(25) — Atlantium flow -
0x0032(50) — Tundra/related flow -
0x00C8(200) — Sandsobar/related flow -
0x01F4(500) — Atlantium travel/training flow
These values align with payment-style prompts and are unlikely to be item IDs.
Observed OP_24 contexts (decoded segments):
-
0x000A(10): Middlegate mapper/travel style prompt flow. -
0x0019(25): Atlantium branch in arena/shop event chain. -
0x0032(50): Atlantium/Vulcania travel prompt family. -
0x00C8(200): training/skill purchase flow. -
0x01F4(500): larger payment gate.
Observed OP_25 contexts:
- decoded values now include
0x0000,0x0001, and0x0002(after endian correction). - remaining
Nonehits are in segments where token alignment is still uncertain after variable-length skip-token control flow.
Current best model:
-
OP_24: gold predicate (amount check to condition flag). -
OP_25: non-gold code predicate (likely word/flag/class/item-code family). -
OP_28: consume-item path; maps cleanly to multiple quest items/keys/discs.
User-verified gate behavior supporting this model:
- bishop key gate: missing key -> blocked with message
- bishop key gate: with key -> allowed and key consumed
- arena tickets: consumed per fight
Use the decoder's predicate extraction mode:
python tools/decode_event.py --predicates EXTRACTED/event.dat--predicates now runs a path-aware, heuristic walk across OP_10/11/2B skip-token
branches so predicate checks behind those branches are surfaced for review.
Use consume-item extraction mode:
python tools/decode_event.py --consumed EXTRACTED/event.dat --items items.dat --answers druids meenu keysThis reports:
-
OP_28consume hits with mapped item IDs/names - per-location context strings
- string hits for candidate word answers (
druids,meenu,keys)
This prints every parsed OP_24/OP_25 hit with:
- location + segment id
- decoded argument value (if recoverable)
- nearby opcode context
- nearby strings for gameplay correlation
tools/decode_event.py now includes:
- opcode map for
0x00..0x32 - raw script preview
- marker-based disassembly pass starting at opcode
0x22candidates
- Docs Wiki Hub ← full doc index
- Getting Started ← read this first
- Overview
- Workspace Notes
- Open Questions
- Full Analysis
- Game Remake
- Startup and Init
- Runtime Memory Map
- Main Loop and Map
- Exploration Input and Options
- Party and Session
- Game State Struct
- Data Hunk mm2 data 00
- Time Era Calendar
- GFX Loading
- ANM TV Format
- PC DOS graphics
- SNES graphics
- 3D View and Game Screen
- Amiga 3D Render Process
- Scripted Scene Graphics
- Event Graphics Opcodes
- Title Screen Assets
- Title Screen Animation
- Format inventory
- items.dat
- monsters.dat
- roster.dat
- attrib.dat
- map.dat
- spells & item use
- event.dat
- Events by location ← 71 maps
- Events hub (numbered)
- Event Script Opcodes
-
Event Script DSL ←
.mm2evtauthoring - Event Text Rendering
- Event Graphics Opcodes
- Combat Overview
- Combat System
- Encounter Tables
- Spell Cast ASM
- monsters.dat abilities
- Spells and item use
- Amiga audio in exe ← Paula / DATA / remake
- Controls: Sounds / Walk Beep
- MM2 music format (superseded)
- Title/death audio paths (superseded)
- Known songs catalog (superseded)
- Town Services
- Spell Sources
- Character Mechanics
- Skills and Hirelings
- Commerce Formulas
- Commerce World Services
- Mount Farview Class Quest
- Class Quest HP Bug
- Event Runtime
- Event to String Path
- Embedded Exe Strings
- Copy Protection
- Time Era Calendar
- Game State Struct
- MM1 Overview ← hub + decode status
- MM1 MAZEDATA format
- MM1 to MM2 outdoor
- MM1 WALLPIX by sector
- MM1 art & graphics
- MM1 items / monsters (status)
- MM1 map walker ↗
- MM1 2D maps gallery
- MM1 WALLPIX gallery