Fix contact transitions and dining car access

This commit is contained in:
2026-05-24 18:14:16 +02:00
parent 510901f5bf
commit eef90f3471
8 changed files with 333 additions and 166 deletions
+31 -5
View File
@@ -78,11 +78,12 @@ Author-facing helper functions live in `data/ink-src/eibenreith/helpers.ink` and
- `time_*`, `day_*`, `slot_*`, `episode_*`: timetable and episode control.
- `meal_*`: arrival-day meal plan.
- `loc_*`, `enter_room`, `present`, `companion_*`: traversal, room setup, and companion presence.
- `first_meeting(character)`, `reunion(character)`, `parting(character)`: contact-manager transitions for the one choice surface after a character first appears, reappears, or stops being present.
- `alone`, `alone_with(character)`: privacy checks for dialogue that should only surface without witnesses or with exactly one companion.
- `state_*`: ordered high-watermark encounter/progress state.
- `mark`, `has`, `lacks`: exact checklist facts.
- `tutorial`: returns true once and marks the tutorial as shown.
- `claim_choice_gate(gate)`: transient choice-surface arbitration. Use only to allow at most one choice from a prioritized family, especially `#auto` groups.
- `claim_choice_gate_if(gate, available)`: transient choice-surface arbitration. Use only to allow at most one valid choice from a prioritized family, especially `#auto` groups.
- `rel_*`: relationship counters and two-value relationship-axis queries.
Relationship counters use only the standard value pairs declared in `characters.ink`:
@@ -131,24 +132,49 @@ Use route and relationship helpers only as heuristics. They should color tone, s
## Choice-Surface Gates
`claim_choice_gate(gate)` returns true only for the first condition that claims the given gate while the current choice surface is being built. It is reset automatically at the start of `provide_choices`.
`claim_choice_gate_if(gate, available)` returns true only for the first valid condition that claims the given gate while the current choice surface is being built. It is reset automatically at the start of `provide_choices`.
Use it when several choices can be valid at the same time but the surface must offer only one of them. The main use case is prioritized `#auto` families:
```ink
* {state_reached(freshen_up_done)} {claim_choice_gate(return_auto)} [AUTO: Viktors Rückkehr nach Frischmachen] #auto:return(2)
+ {claim_choice_gate_if(return_auto, reunion(viktor) && state_reached(freshen_up_done))} [AUTO: Viktors Rückkehr nach Frischmachen] #auto
...
-> TURN
* {state_reached(explore_train_done)} {claim_choice_gate(return_auto)} [AUTO: Viktors Rückkehr nach Erkundung] #auto:return(2)
+ {claim_choice_gate_if(return_auto, reunion(viktor) && state_reached(explore_train_done))} [AUTO: Viktors Rückkehr nach Erkundung] #auto
...
-> TURN
```
The source order of the atoms is the priority order. Auto choices are not randomized by the UI; the first ready auto choice is selected like a normal Ink choice. The gate prevents lower-priority choices from surfacing on the same choice build. Put `claim_choice_gate(...)` last in the condition list, after all ordinary availability checks, so a false candidate cannot consume the gate. The shared `#auto:key(delay)` prevents the same auto family from firing again too soon on later builds. Use the colon form for keyed auto tags on choice lines; bracketed `#auto[key](delay)` is not Ink-compatible there.
The source order of the atoms is the priority order. Auto choices are not randomized by the UI; the first ready auto choice is selected like a normal Ink choice. The gate prevents lower-priority choices from surfacing on the same choice build. Use `claim_choice_gate_if(gate, available)` when availability is conditional; pass the whole availability expression as the second parameter so false candidates cannot consume the gate.
Do not use gates as story memory. World state belongs in Ink callbacks, `state_*` progress chains, or `mark/has/lacks` facts.
## Character Contact Manager
Character contact is managed centrally in `helpers.ink`. Authors should not add room-specific flags such as `viktor_back_in_compartment`, `met_viktor_here`, or `seen_viktor_leave`. `loc_move_to(...)` updates the player location, moves active companions, and refreshes contact state.
Use:
```ink
{present(viktor)}
{first_meeting(viktor)}
{reunion(viktor)}
{parting(viktor)}
{alone()}
{alone_with(viktor)}
```
`first_meeting`, `reunion`, and `parting` are transition checks. They are true only for the first choice surface after the transition happened. The next turn clears them centrally in `provide_choices`. Authors must not call `contact_clear_transitions()` or any other cleanup helper from content. This makes the transitions suitable for immediate one-shot auto reactions:
```ink
+ {claim_choice_gate_if(return_auto, reunion(viktor) && state_reached(freshen_up_done))} [AUTO: Viktors Rückkehr nach Frischmachen] #auto
...
-> TURN
```
Companions are characters in the `companions` list. When Valerie traverses with `loc_move_to(...)`, companions automatically move to the new location before contact is updated. `companion_join(character)` and `companion_leave(character)` only change whether a character follows Valerie; they are not story-memory flags. Character starting positions and initial companion state belong in episode setup. If several characters are placed manually before play resumes, call `contact_sync()` once after setup to establish contact without firing a meeting or reunion reaction.
## Implemented Tag Forms
Use bracket tags for titles, filenames, and longer text: