♥ lp-engine

Chapter 3

Scripts and events

Every conversation in LovePlus+ is a small program. Each script is pure content: it says what happens in a scene. Which scene plays today is decided somewhere else entirely, by a pipeline of 36 rules built into the game's code.

The script machine

The scenes live in one archive of 1,304 script files, holding 16,332 entry points between them. Each is bytecode for a small virtual machine, Script_ExecuteOpcode. It's a simple design: an accumulator, a stack, a flag for branches, variables, and about sixty instructions for arithmetic, comparisons, jumps, calls and text.

One instruction does almost all the real work: call command. A script pushes its arguments and a count, then names a command, and Script_DispatchCommand looks it up in a table of 553 entries, 526 of them implemented. The handlers are tiny (the median is 20 bytes); each one hands its arguments to the part of the game that does the job. Across all the scripts, the most used commands are:

commanduseswhat it does
0x0136,600wait until the scene's current actions have finished
0x0f34,000switch the heroine to another motion, blending into it
0x3431,100set her facial expression (one of 45 moods)
0x5c27,200play a voice clip
0x4420,300wait a number of scene updates (about 15 a second)
0x5511,300set a game flag
0x0d / 0x0e10,900fade out / fade in
0x064,200choose a camera angle

Tasks: how a script waits

Most commands don't act at once. They describe a task: a kind, a duration and a few parameters. They queue it for TaskList_TickTaskMain, an 11 KB function that knows about 195 kinds of task (motions, camera moves, fades, waits, map changes). The script keeps going until it reaches command 0x01, which pauses it until every queued task is done. That's how a single scene line can start a motion, a voice clip and a camera move together, then wait for all three.

Dialogue

Each script carries its own text: 42,355 lines across the game, 23,763 of them voiced (the player's own lines never are). A handful of instructions build the dialogue box: clear a buffer, append a line from the script's table, append a variable such as your name, then show it and wait for a tap. Buffer 0 holds the message and buffer 2 the speaker's name.

Scripts make remarkably few decisions. There are only about 41 uses of the choice commands in the whole game, and almost no changes to her feelings or your stats. Those rules live in the game's code, which decides both which scene you get and what it does to your relationship.

Finding a script

Every scene is known by a 32-bit id. The top byte says whose scene it is: 0xA0 for Manaka, 0x58 for Rinko, 0x08 for Nene, and 0x80 for scenes that don't depend on the heroine. The rest is a key, so the same moment for each heroine shares a key. Manaka's first meeting is 0xA0006600 and Rinko's is 0x58006600. Long events continue with the next ids: …3400, …3401, …3402.

One script, number 899, is the directory: 16,332 sorted pairs of id and location, one for every entry point in the game. ScriptMgr_Start takes an id, finds it there, and starts the script, or queues it behind the one that's running. ScriptMgr_Update runs it a step each frame. (Both were first labelled as clock functions; the map notes their old names.)

Which scene plays

This is the heart of the game. When a period of the day begins, EventState_Initialize asks the event session what should happen, and the session works it out in three steps.

1. The context

EventCond_BuildContextWord packs the present moment into a single word of bits: your relationship stage with the heroine, her mood, the time of day (one of six periods), the season, and whether today is a special day. It also takes in your schedule, the plan you enter in the game: four periods a day, an activity for each, such as studying, a part-time job, or meeting one of the three heroines after school.

2. Thirty-six stages

EventSession_RunStages walks a table of 36 stages in order. Each stage names the contexts it applies to and a handler. The first stage whose handler returns a script id wins. Some examples:

stagewhat it looks at
6, 7, 11your part-time job, if you scheduled one: a scene at work
9a dated plan with a heroine, such as a date you arranged
12the calendar: holidays, school events, the day's rule (chapter 4)
18an afternoon meeting with a heroine: a random unseen pick from her candidate list
20, 21scenes by heroine and relationship stage, when a date event is available
27, 31text events and messages tied to the schedule

3. Candidate lists

Many stages choose from lists that are data, not code: 688 small files holding 2,920 records between them. A record is a scene id, a priority, a seen flag and up to three groups of conditions. A record qualifies when any of its groups passes, and a group passes when all its conditions hold. The conditions use 107 operations from a table of 237 (compare a stat, test a flag, check the day, look at her mood…), and CondList_CollectPassingRecords applies them. Then the stage picks: often the first unseen record, sometimes a random one, using the game's random generator Rand_Next, seeded from the clock.

Once a scene has played, EventLogic_RunEventStateMachine applies what it means: growth in your four stats, the heroine's event meters, entries in the log, flags for later scenes.

A note on names

Much of this machinery was first labelled as something else. The event engine's functions sat in a block named MicSession_* and MicSequence_*, as if they belonged to the microphone. Following the game through a week of real play showed what they are. In the function map they're now EventSession_* and EventCond_*, and each notes its old name. A few neighbours whose job is still unconfirmed keep their names, marked low, with a note that the "Mic" is wrong.

How we know

What's still open. The stages and conditions are exact, but some of the values they read are only partly decoded: a few calendar rules, the meaning of some flags, and a long tail of single bytes in the heroine's save record. The detailed notes are in SCRIPT_VM.md, EVENT_SELECTION.md and EVENT_STAGES.md.