<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://Selrach2040.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Textdata_event.txt</id>
	<title>Textdata event.txt - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://Selrach2040.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Textdata_event.txt"/>
	<link rel="alternate" type="text/html" href="https://Selrach2040.com/wiki/index.php?title=Textdata_event.txt&amp;action=history"/>
	<updated>2026-07-04T07:58:51Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.4</generator>
	<entry>
		<id>https://Selrach2040.com/wiki/index.php?title=Textdata_event.txt&amp;diff=53&amp;oldid=prev</id>
		<title>127.0.0.1: Technical breakdown of event.txt, from event_txt_format.md (via create-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://Selrach2040.com/wiki/index.php?title=Textdata_event.txt&amp;diff=53&amp;oldid=prev"/>
		<updated>2026-07-02T11:21:28Z</updated>

		<summary type="html">&lt;p&gt;Technical breakdown of event.txt, from event_txt_format.md (via create-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;event.txt&amp;lt;/code&amp;gt;&amp;#039;&amp;#039;&amp;#039; (268,065 bytes, part of [[BIN textdata]]) is the largest textdata file: a time-scheduled world-event / object-scripting table. Each row schedules a scripted action (object state change, sound, lighting, scene transition) on a specific world object / map-area node / zone at a specific in-game calendar time.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Important:&amp;#039;&amp;#039;&amp;#039; in this build the client&amp;#039;s time-driven dispatch of these rows is permanently gated off (see [[#Dispatch gating|Dispatch gating]] below) — the rows are meant to be &amp;#039;&amp;#039;&amp;#039;server-driven&amp;#039;&amp;#039;&amp;#039;. The server should walk this schedule and emit &amp;lt;code&amp;gt;0xC062&amp;lt;/code&amp;gt; effect packets at the scheduled ticks.&lt;br /&gt;
&lt;br /&gt;
== Loader / parser (Ghidra) ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function !! Addr !! Role&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Struct_Setup_Effect_Manager&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;00498546&amp;lt;/code&amp;gt; || opens &amp;lt;code&amp;gt;event.txt&amp;lt;/code&amp;gt;, parses every row, builds the trees&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;Archive_OpenTextFileByName&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;004bc800&amp;lt;/code&amp;gt; || opens the file from &amp;lt;code&amp;gt;g_NewArchive2&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TextAsset_ReadNextLine&amp;lt;/code&amp;gt; || — || row iterator&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;TickKey_InitFromCoords&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;TickKey_ComputeAndDecompose&amp;lt;/code&amp;gt; || &amp;lt;code&amp;gt;004b50c0&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;004b5250&amp;lt;/code&amp;gt; || converts cols 2–6 (calendar) → absolute tick key&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;EffectDef_AppendKeyframe&amp;lt;/code&amp;gt; || — || stores the row payload as a keyframe on the effect def&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Parse loop (paraphrased):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
while (TextAsset_ReadNextLine(&amp;amp;buf, line)) {&lt;br /&gt;
    if (line[0] == &amp;#039;#&amp;#039;) continue;                       // &amp;#039;#&amp;#039; = comment line&lt;br /&gt;
    if (sscanf(line, &amp;quot;%d,%d,...,%d&amp;quot;, &amp;amp;c1..&amp;amp;c16) != 16)   // exactly 16 ints&lt;br /&gt;
        continue;                                        // malformed -&amp;gt; skip&lt;br /&gt;
    if (c12 &amp;lt; 10000) c12 -= 1;                            // sprite id -&amp;gt; 0-based for &amp;lt;10000&lt;br /&gt;
    key  = TickKey(c2,c3,c4,c5,c6);                       // week,day,hour,min,sec -&amp;gt; tick&lt;br /&gt;
    tree = (c1 == 0) ? ground_effect_tree : air_effect_tree;&lt;br /&gt;
    ... insert effect def at key into tree, append keyframe(c7..c16) ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Format string &amp;lt;code&amp;gt;&amp;quot;%d,%d,…,%d&amp;quot;&amp;lt;/code&amp;gt; (16 fields) @ &amp;lt;code&amp;gt;004e4dd4&amp;lt;/code&amp;gt;. Lines beginning with &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; are comments; any line that doesn&amp;#039;t scan to exactly 16 ints is skipped.&lt;br /&gt;
&lt;br /&gt;
== Column layout (16 comma-separated ints) ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! # !! Name !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| 1 || layer / kind || &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; → ground tree, &amp;lt;code&amp;gt;≠0&amp;lt;/code&amp;gt; → air tree. In practice always ≥ 1 (no row is 0), so every row goes to the air tree — the gated-off path.&lt;br /&gt;
|-&lt;br /&gt;
| 2 || week || in-game week (1-based). &amp;lt;code&amp;gt;99&amp;lt;/code&amp;gt; = recurring/any week — selects a separate keyframe layout.&lt;br /&gt;
|-&lt;br /&gt;
| 3 || day || in-game day (1-based).&lt;br /&gt;
|-&lt;br /&gt;
| 4 || hour || 0–23.&lt;br /&gt;
|-&lt;br /&gt;
| 5 || minute || 0–59.&lt;br /&gt;
|-&lt;br /&gt;
| 6 || second || 0–59.&lt;br /&gt;
|-&lt;br /&gt;
| 7 || aux param A || effect payload; only used when week ≠ 99 (one-shot events).&lt;br /&gt;
|-&lt;br /&gt;
| 8 || aux param B || effect payload.&lt;br /&gt;
|-&lt;br /&gt;
| 9 || keyframe_count || &amp;lt;code&amp;gt;Effect_Def.keyframe_count&amp;lt;/code&amp;gt; (struct off 40).&lt;br /&gt;
|-&lt;br /&gt;
| 10 || keyframe_start || &amp;lt;code&amp;gt;Effect_Def.keyframe_start&amp;lt;/code&amp;gt; (struct off 44).&lt;br /&gt;
|-&lt;br /&gt;
| 11 || keyframe_end || &amp;lt;code&amp;gt;Effect_Def.keyframe_end&amp;lt;/code&amp;gt; (struct off 48).&lt;br /&gt;
|-&lt;br /&gt;
| 12 || target id || object/node/zone the action runs on: &amp;lt;code&amp;gt;&amp;amp;lt; 10000&amp;lt;/code&amp;gt; → world object; &amp;lt;code&amp;gt;&amp;amp;gt;= 10001&amp;lt;/code&amp;gt; → map-area node = &amp;lt;code&amp;gt;world.wlist&amp;lt;/code&amp;gt; record &amp;lt;code&amp;gt;id − 10001&amp;lt;/code&amp;gt;; &amp;lt;code&amp;gt;11190&amp;lt;/code&amp;gt; → zone. If &amp;lt;code&amp;gt;&amp;amp;lt; 10000&amp;lt;/code&amp;gt; it&amp;#039;s decremented to a 0-based index. (struct off 56)&lt;br /&gt;
|-&lt;br /&gt;
| 13 || mode / action || which &amp;lt;code&amp;gt;Event_Dispatch&amp;lt;/code&amp;gt; action runs (state change / sound / lighting+transition). Values 0, 1, 3 get special count handling (col 14). (struct off 60)&lt;br /&gt;
|-&lt;br /&gt;
| 14 || count / repeat || if &amp;lt;code&amp;gt;mode∈{1,3}&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;≠99&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;−1&amp;lt;/code&amp;gt;; if &amp;lt;code&amp;gt;mode==0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;==0&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;−1&amp;lt;/code&amp;gt;. &amp;lt;code&amp;gt;99&amp;lt;/code&amp;gt; kept as &amp;quot;infinite/any&amp;quot;. (struct off 64)&lt;br /&gt;
|-&lt;br /&gt;
| 15 || aux param C || action payload (e.g. sound id / state value). (struct off 68)&lt;br /&gt;
|-&lt;br /&gt;
| 16 || aux param D || action payload. (struct off 52)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Cols 9/10/11/13/14 map to named &amp;lt;code&amp;gt;Effect_Def&amp;lt;/code&amp;gt; fields and col 12 is the dispatch target id. Cols 7/8/15/16 land in currently-unnamed &amp;lt;code&amp;gt;Effect_Def&amp;lt;/code&amp;gt; offsets (32/36/52/68) and carry the per-action payload — treat these column-to-payload bindings as provisional.&lt;br /&gt;
&lt;br /&gt;
== The calendar / tick key (cols 2–6) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;TickKey_ComputeAndDecompose @ 004b5250&amp;lt;/code&amp;gt; converts &amp;lt;code&amp;gt;(week, day, hour, minute, second)&amp;lt;/code&amp;gt; into an absolute tick. Calendar is 7-day weeks, 24-hour days; week/day are 1-indexed, hour/minute/second are 0-indexed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
day_index = (week - 1) * 7 + day            // stored at Effect_Def off 8 (tick_raw)&lt;br /&gt;
tick      = (day_index - 1) * 86400&lt;br /&gt;
          + hour * 3600 + minute * 60 + second   // stored at Effect_Def off 4 (tick_value)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A negative &amp;lt;code&amp;gt;tick&amp;lt;/code&amp;gt; marks the entry invalid. &amp;lt;code&amp;gt;week == 99&amp;lt;/code&amp;gt; is the wildcard for recurring effects. Full derived tick constants: 1 week base, 1 day base, 7 days/week, 24 hours/day, 60 min/hour, 60 sec/min (all read from &amp;lt;code&amp;gt;rg.exe&amp;lt;/code&amp;gt; &amp;lt;code&amp;gt;.data&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Example rows ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1,1,1,22,0,0,0,0,0,0,0,11308,1,44,0,1&lt;br /&gt;
1,1,1,22,0,0,0,0,0,0,0,11425,1,99,0,1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Row 1: week 1 / day 1 / &amp;#039;&amp;#039;&amp;#039;22:00:00&amp;#039;&amp;#039;&amp;#039;, target id &amp;#039;&amp;#039;&amp;#039;11308&amp;#039;&amp;#039;&amp;#039;, mode 1, count 44. Row 2: same time, target 11425, count &amp;#039;&amp;#039;&amp;#039;99&amp;#039;&amp;#039;&amp;#039; (kept = recurring/infinite).&lt;br /&gt;
&lt;br /&gt;
== What a fired row does (&amp;lt;code&amp;gt;Event_Dispatch @ 00497ae0&amp;lt;/code&amp;gt;) ==&lt;br /&gt;
&lt;br /&gt;
When a row&amp;#039;s tick arrives it is not rendered as a free particle. &amp;lt;code&amp;gt;Event_Dispatch&amp;lt;/code&amp;gt; looks up a target by id and runs a scripted action on it:&lt;br /&gt;
&lt;br /&gt;
* id &amp;lt;code&amp;gt;&amp;amp;lt; 10000&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;GameWorld_GetObject&amp;lt;/code&amp;gt; (a &amp;lt;code&amp;gt;World_Zone_Entry&amp;lt;/code&amp;gt;); id &amp;lt;code&amp;gt;&amp;amp;gt;= 10001&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;GameWorld_GetWorldNode(id - 10001)&amp;lt;/code&amp;gt; (a &amp;lt;code&amp;gt;World_Node&amp;lt;/code&amp;gt;); sentinel &amp;lt;code&amp;gt;11190&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;GameWorld_GetZone(...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
* action type (col 13) switches between: change the target&amp;#039;s state slots, load a sound (&amp;lt;code&amp;gt;SoundManager_Load_Single_SSF&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;TryLoad_SoundEffect&amp;lt;/code&amp;gt;), or set lighting + trigger a scene transition (&amp;lt;code&amp;gt;Lighting_SetInstant&amp;lt;/code&amp;gt; → &amp;lt;code&amp;gt;GameState_Transition(0xf)&amp;lt;/code&amp;gt;).&lt;br /&gt;
* it only acts if the player is in the target&amp;#039;s area, gated by &amp;lt;code&amp;gt;target-&amp;amp;gt;Vtable[5](player_x, player_y, player_z, active_area)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;The &amp;lt;code&amp;gt;&amp;amp;gt;= 10001&amp;lt;/code&amp;gt; target is a map-area NODE, not a character NPC.&amp;#039;&amp;#039;&amp;#039; &amp;lt;code&amp;gt;GameWorld_GetWorldNode&amp;lt;/code&amp;gt; returns &amp;lt;code&amp;gt;g_pGameWorld-&amp;amp;gt;node_array[id - 10001]&amp;lt;/code&amp;gt; — a &amp;lt;code&amp;gt;World_Node&amp;lt;/code&amp;gt; carrying that area&amp;#039;s interaction_type, sound_index, and per-node BGM/SE/effect arrays. The index maps 1:1 to &amp;lt;code&amp;gt;world.wlist&amp;lt;/code&amp;gt; record order (1,471 area records, id range 10001–11471). Verified: node 0 (id 10001) = &amp;lt;code&amp;gt;field&amp;lt;/code&amp;gt; (overworld), node 1307 (id 11308) = &amp;lt;code&amp;gt;1ta16&amp;lt;/code&amp;gt;, node 1124 (id 11425) = &amp;lt;code&amp;gt;ir_b2p06&amp;lt;/code&amp;gt;. So &amp;lt;code&amp;gt;event.txt&amp;lt;/code&amp;gt; wires scheduled night-time ambiance (sounds/effects/BGM) to specific map areas, never to characters.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;/effect 1 0&amp;lt;/code&amp;gt; console command (0xC062, channel 1) reaches this same machinery directly (&amp;lt;code&amp;gt;EffectManager_ScheduleKeyframesForObject&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== Dispatch gating — why the client never self-fires these ==&lt;br /&gt;
&lt;br /&gt;
The per-frame dispatcher &amp;lt;code&amp;gt;EffectManager_TickAndDispatchEffects @ 00498da0&amp;lt;/code&amp;gt; is called from &amp;lt;code&amp;gt;GameStateMachine_Update @ 004a4180&amp;lt;/code&amp;gt; and processes two trees:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;ground_effect_tree&amp;lt;/code&amp;gt; (col1 == 0) — dispatched unconditionally, but empty: of 6,590 rows, none have col1 == 0.&lt;br /&gt;
* &amp;lt;code&amp;gt;air_effect_tree&amp;lt;/code&amp;gt; (col1 != 0) — holds every row, dispatched only inside &amp;lt;code&amp;gt;if (g_NetworkGlobal-&amp;amp;gt;field4_0x8 == 0)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;field4_0x8&amp;lt;/code&amp;gt; is written exactly once — in &amp;lt;code&amp;gt;Struct_Setup_Net0x98_0x1C @ 004b7010&amp;lt;/code&amp;gt;, and its only caller passes a literal &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;. No state, command, or packet ever clears it. So the air path is permanently off, and the client&amp;#039;s time-driven &amp;lt;code&amp;gt;event.txt&amp;lt;/code&amp;gt; dispatch never runs in normal play. Two further gates apply even if that flag were 0: the dispatcher only runs on a minute boundary (&amp;lt;code&amp;gt;if (nSecond == 0)&amp;lt;/code&amp;gt;), and &amp;lt;code&amp;gt;Event_Dispatch&amp;lt;/code&amp;gt; only acts when &amp;lt;code&amp;gt;g_EffectSystem_Initialized == 0&amp;lt;/code&amp;gt; (armed on world-entry).&lt;br /&gt;
&lt;br /&gt;
== Server relevance ==&lt;br /&gt;
&lt;br /&gt;
Because client self-dispatch is fused off, &amp;lt;code&amp;gt;event.txt&amp;lt;/code&amp;gt; is effectively the &amp;#039;&amp;#039;&amp;#039;server&amp;#039;s schedule&amp;#039;&amp;#039;&amp;#039;. To reproduce original behavior the server should:&lt;br /&gt;
&lt;br /&gt;
# Drive an in-game clock (week/day/hour/min/sec → tick) using the calendar constants above.&lt;br /&gt;
# At each row&amp;#039;s tick, send the matching &amp;lt;code&amp;gt;0xC062&amp;lt;/code&amp;gt; effect packet to clients in the target&amp;#039;s area (col 12 resolves to a map area via &amp;lt;code&amp;gt;world.wlist&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;id − 10001&amp;lt;/code&amp;gt;, so the broadcast can be filtered to players actually in that area).&lt;br /&gt;
&lt;br /&gt;
The clock bases must match the client&amp;#039;s (set via &amp;lt;code&amp;gt;0xC05C&amp;lt;/code&amp;gt; &amp;quot;/timeconfig&amp;quot; + &amp;lt;code&amp;gt;0xC05D&amp;lt;/code&amp;gt; &amp;quot;/time&amp;quot;) so server schedule and client clock stay in sync.&lt;br /&gt;
&lt;br /&gt;
== Open items ==&lt;br /&gt;
&lt;br /&gt;
* Trace the keyframe→task copy to pin the exact column → &amp;lt;code&amp;gt;Event_Dispatch param_1[0..3]&amp;lt;/code&amp;gt; mapping, and name &amp;lt;code&amp;gt;Effect_Def&amp;lt;/code&amp;gt; offsets 32/36/52/68.&lt;br /&gt;
* Map &amp;lt;code&amp;gt;mode&amp;lt;/code&amp;gt; (col 13) values to the concrete &amp;lt;code&amp;gt;Event_Dispatch&amp;lt;/code&amp;gt; action types (0–4).&lt;br /&gt;
* Cross-check target ids (col 12, &amp;lt;code&amp;gt;11xxx&amp;lt;/code&amp;gt;) against &amp;lt;code&amp;gt;world.wlist&amp;lt;/code&amp;gt; area codes to label which area each event scripts.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[BIN textdata]]&lt;br /&gt;
* [[textdata bgmtable.txt]], [[textdata tenkoutable.txt]], [[textdata tensoutable.txt]] — the related time-windowed per-area tables&lt;/div&gt;</summary>
		<author><name>127.0.0.1</name></author>
	</entry>
</feed>