Checkpoint ink architecture updates

This commit is contained in:
2026-05-24 16:00:22 +02:00
parent 01c8b1aff6
commit 510901f5bf
14 changed files with 1218 additions and 643 deletions
+174 -1
View File
@@ -43,6 +43,7 @@ VAR current_episode_bucket = -> empty_bucket
VAR current_episode_start = -> empty_bucket
VAR current_episode_end = -> empty_bucket
VAR current_game_bucket = -> game_bucket
VAR claimed_choice_gates = ()
VAR slot_early_morning_episode = no_episode
VAR slot_mid_morning_episode = episode_train_intro
@@ -78,6 +79,7 @@ VAR slot_late_night_episode = no_episode
=== provide_choices ===
~ claimed_choice_gates = ()
{
- room_seen_on_enter():
<- current_moment_bucket
@@ -92,6 +94,34 @@ VAR slot_late_night_episode = no_episode
-> DONE
// Claim a transient gate for this single choice-surface build.
//
// This is not story state. It is cleared at the start of provide_choices and is
// used only to arbitrate between multiple choices that could otherwise surface
// together, especially prioritized #auto choices. The first condition in source
// order that claims a gate returns true; later claims of the same gate return
// false until the next choice surface is built.
//
// Use sparingly, and only where the surface must offer at most one item from a
// family. Normal story memory belongs in Ink callbacks, state_* chains, or
// mark/has/lacks facts.
//
// Example:
// Put claim_choice_gate last in the condition list. Earlier conditions should
// prove that the choice is valid before this side-effecting helper consumes the
// gate.
//
// * {state_reached(freshen_up_done)} {claim_choice_gate(return_auto)} [...] #auto:return(2)
=== function claim_choice_gate(gate) ===
{
- claimed_choice_gates ? gate:
~ return false
- else:
~ claimed_choice_gates += gate
~ return true
}
=== empty_bucket ===
-> DONE
@@ -157,6 +187,18 @@ VAR route_eccentric = 0
// - route_reached(route, amount): true when the counter is >= amount.
// - route_between(route, min, max): true when the counter is within the
// inclusive range min..max.
// - route_value(route_id): returns a route counter by RouteId LIST item.
// - route_total(): total number of route-marking choices taken.
// - route_beats(route_id, other_route_id, margin): true when one route is ahead
// of another by at least margin.
// - route_repeated(route_id, amount): true when a route has been chosen at
// least amount times. Use for "player keeps choosing this mode" heuristics.
// - route_is_highest(route_id): true when a route is tied for or holds the
// highest current route total and has been chosen at least once.
// - route_is_clear(route_id, margin): true when a route leads every other route
// by at least margin. Use this for strong heuristic tone changes.
// - route_share_reached(route_id, numerator, denominator): true when a route is
// at least numerator/denominator of all route-marking choices.
//
// Route counters are simple totals, not Ingold-style two-value axes. Do not use
// them with relationship matrix helpers.
@@ -194,6 +236,76 @@ VAR route_eccentric = 0
=== function route_between(value, min, max) ===
~ return value >= min && value <= max
=== function route_value(route_id) ===
{
- route_id ? composure:
~ return route_composure
- route_id ? detective:
~ return route_detective
- route_id ? lover:
~ return route_lover
- route_id ? sapphic:
~ return route_sapphic
- route_id ? careless:
~ return route_careless
- route_id ? eccentric:
~ return route_eccentric
- else:
~ return 0
}
=== function route_total() ===
~ return route_composure + route_detective + route_lover + route_sapphic + route_careless + route_eccentric
=== function route_best_value() ===
{
- route_composure >= route_detective && route_composure >= route_lover && route_composure >= route_sapphic && route_composure >= route_careless && route_composure >= route_eccentric:
~ return route_composure
- route_detective >= route_composure && route_detective >= route_lover && route_detective >= route_sapphic && route_detective >= route_careless && route_detective >= route_eccentric:
~ return route_detective
- route_lover >= route_composure && route_lover >= route_detective && route_lover >= route_sapphic && route_lover >= route_careless && route_lover >= route_eccentric:
~ return route_lover
- route_sapphic >= route_composure && route_sapphic >= route_detective && route_sapphic >= route_lover && route_sapphic >= route_careless && route_sapphic >= route_eccentric:
~ return route_sapphic
- route_careless >= route_composure && route_careless >= route_detective && route_careless >= route_lover && route_careless >= route_sapphic && route_careless >= route_eccentric:
~ return route_careless
- route_eccentric >= route_composure && route_eccentric >= route_detective && route_eccentric >= route_lover && route_eccentric >= route_sapphic && route_eccentric >= route_careless:
~ return route_eccentric
- else:
~ return 0
}
=== function route_beats(route_id, other_route_id, margin) ===
~ return route_value(route_id) - route_value(other_route_id) >= margin
=== function route_repeated(route_id, amount) ===
~ return route_value(route_id) >= amount
=== function route_is_highest(route_id) ===
~ return route_value(route_id) > 0 && route_value(route_id) >= route_best_value()
=== function route_is_clear(route_id, margin) ===
{
- route_id ? composure:
~ return route_composure > 0 && route_composure - route_detective >= margin && route_composure - route_lover >= margin && route_composure - route_sapphic >= margin && route_composure - route_careless >= margin && route_composure - route_eccentric >= margin
- route_id ? detective:
~ return route_detective > 0 && route_detective - route_composure >= margin && route_detective - route_lover >= margin && route_detective - route_sapphic >= margin && route_detective - route_careless >= margin && route_detective - route_eccentric >= margin
- route_id ? lover:
~ return route_lover > 0 && route_lover - route_composure >= margin && route_lover - route_detective >= margin && route_lover - route_sapphic >= margin && route_lover - route_careless >= margin && route_lover - route_eccentric >= margin
- route_id ? sapphic:
~ return route_sapphic > 0 && route_sapphic - route_composure >= margin && route_sapphic - route_detective >= margin && route_sapphic - route_lover >= margin && route_sapphic - route_careless >= margin && route_sapphic - route_eccentric >= margin
- route_id ? careless:
~ return route_careless > 0 && route_careless - route_composure >= margin && route_careless - route_detective >= margin && route_careless - route_lover >= margin && route_careless - route_sapphic >= margin && route_careless - route_eccentric >= margin
- route_id ? eccentric:
~ return route_eccentric > 0 && route_eccentric - route_composure >= margin && route_eccentric - route_detective >= margin && route_eccentric - route_lover >= margin && route_eccentric - route_sapphic >= margin && route_eccentric - route_careless >= margin
- else:
~ return false
}
=== function route_share_reached(route_id, numerator, denominator) ===
~ temp total = route_total()
~ return total > 0 && route_value(route_id) * denominator >= total * numerator
// ============================================================================
// TIME-SLOT HELPERS
// ============================================================================
@@ -409,6 +521,8 @@ VAR route_eccentric = 0
// - accompanied_by(character): true when character is in the companion list.
// - companion_join(character), companion_leave(character): update companions.
// - present(character): true when a character's location equals current_location.
// - alone(): true when no tracked NPC is currently present with Valerie.
// - alone_with(character): true when exactly the given tracked NPC is present.
// - character_move_to(character, location): move a known NPC independently.
//
// Structural helper:
@@ -448,6 +562,17 @@ VAR route_eccentric = 0
~ return false
}
=== function alone() ===
~ return not present(viktor)
=== function alone_with(character) ===
{
- character == viktor:
~ return present(viktor)
- else:
~ return false
}
=== function character_move_to(character, location) ===
{
- character == viktor:
@@ -503,9 +628,13 @@ VAR story_state = ()
//
// Author-facing helpers:
// - state_reach(state_or_states): advance one or more ordered LIST states.
// - state_reach_if_started(state_or_states): advance only chains that already
// have any reached state; useful when one action can complete an already
// started task but must not start it retroactively.
// - state_move_to(state): readable alias for state_reach(state).
// - state_reached(state), state_before(state), state_between(a, b),
// state_is(state), state_current(state): query ordered progress.
// state_started(state), state_unstarted(state), state_is(state),
// state_current(state): query ordered progress.
// - mark(fact_or_facts), has(fact_or_facts), lacks(fact_or_facts): manage exact
// checklist facts.
// - tutorial(tutorial_fact): returns true once and marks the tutorial as shown.
@@ -575,6 +704,27 @@ VAR story_state = ()
}
// Return true if any state in the same LIST chain has been reached.
// This is the generic "not null" check for a progress tracker.
//
// Use when content needs to know whether a linear process has begun at all,
// without caring which exact step is current.
//
// Example:
// {state_started(freshen_up_done): ...}
=== function state_started(state) ===
~ return LIST_COUNT(story_state ^ LIST_ALL(state)) > 0
// Return true if no state in the same LIST chain has been reached.
// This is the generic null-state check for a progress tracker.
//
// Example:
// {state_unstarted(freshen_up_started): ...}
=== function state_unstarted(state) ===
~ return not state_started(state)
// Mark a state, or a list of states, as reached.
// High-watermark behavior:
// reaching a state automatically reaches all earlier states in the same LIST chain.
@@ -600,6 +750,29 @@ VAR story_state = ()
}
// Mark a state, or list of states, as reached only if its own LIST chain has
// already begun.
//
// Use this for actions that can satisfy an already stated intention or progress
// an already active line, but must not create that line retroactively.
//
// Example:
// ~ state_reach_if_started(freshen_up_done)
// ~ state_reach_if_started((freshen_up_done, unwell_managed))
=== function state_reach_if_started(states_to_set) ===
~ temp x = state_pop(states_to_set)
{
- not x:
~ return false
- state_started(x):
~ return state_reach(x) || state_reach_if_started(states_to_set)
- else:
~ return false || state_reach_if_started(states_to_set)
}
// Return true if every given independent fact has been marked.
// Facts are exact checklist items, not forward-moving encounter states.
// Example: