// ============================================================================ // EIBENREITH 00 HELPERS // ============================================================================ // Include after global LIST definitions in Eibenreith.ink and before all // character relationship and chapter files. // // Contains: // - global non-character variables // - route counters and route helper functions // - time-slot, meal-plan, episode, location, and traversal helpers // - global story-state storage and LIST/state-tree helpers // - relationship helper functions // // Does not contain: // - global LIST declarations // - chapter-specific LIST declarations // - character relationship variables // - chapter prose or scene knots // ============================================================================ // ============================================================================ // GLOBAL SCHEDULE / LOCATION / EPISODE VARIABLES // ============================================================================ // The LIST values used here are declared in Eibenreith.ink before this file is // included. VAR current_day = 1 VAR current_slot = mid_morning VAR active_episode = episode_train_intro VAR last_slot_outcome = "normal" VAR meal_plan = meal_unset VAR current_location = loc_train_compartment VAR companions = () VAR current_room_entry_bucket = -> empty_bucket VAR current_moment_bucket = -> empty_bucket VAR current_room_look_bucket = -> empty_bucket VAR current_room_exit_bucket = -> empty_bucket 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 slot_early_morning_episode = no_episode VAR slot_mid_morning_episode = episode_train_intro VAR slot_noon_episode = episode_station_midday VAR slot_afternoon_episode = episode_carriage_ride VAR slot_evening_episode = episode_first_dinner VAR slot_early_night_episode = no_episode VAR slot_late_night_episode = no_episode // ============================================================================ // INTRO / CHARACTER-GENERATION VARIABLES // ============================================================================ // These values are defined during chapter 01, but they are global character // facts and may be referenced by later chapters. VAR tut_choice_intro = false VAR tut_traversal_intro = false VAR tut_character_intro = false VAR tut_dialog_intro = false VAR tut_optional_intro = false VAR class = () VAR title_part = "" VAR given_names = "" VAR common_name = "" VAR surname = "" VAR birthplace = "" VAR birthdate = "" VAR zodiac = "" VAR relig = () VAR belief = () VAR body = () VAR outfit = () VAR hair = () VAR style = () VAR face = () // ============================================================================ // VALERIE ROUTE VALUES // ============================================================================ // These are counters, not mutually exclusive classes. // They describe the strategies the player repeatedly chooses for Valerie. VAR route_composure = 0 VAR route_detective = 0 VAR route_lover = 0 VAR route_sapphic = 0 VAR route_careless = 0 VAR route_eccentric = 0 // ============================================================================ // ROUTE HELPERS // ============================================================================ // Ink does not support overloaded functions or optional parameters. // Therefore: // - route_inc(route) increases by 1 // - route_inc_by(route, amount) increases by amount === function route(value) === ~ return value === function route_inc(ref value) === ~ value += 1 === function route_inc_by(ref value, amount) === ~ value += amount === function route_dec(ref value) === ~ value -= 1 === function route_dec_by(ref value, amount) === ~ value -= amount === function route_move_to(ref value, amount) === ~ value = amount === function route_clear(ref value) === ~ value = 0 === function route_is(value, amount) === ~ return value == amount === function route_before(value, amount) === ~ return value <= amount === function route_reached(value, amount) === ~ return value >= amount === function route_between(value, min, max) === ~ return value >= min && value <= max === function route_min(a, b) === { - a <= b: ~ return a - else: ~ return b } === function route_max(a, b) === { - a >= b: ~ return a - else: ~ return b } === function route_diff(positive, negative) === ~ return positive - negative === function route_diff_reached(positive, negative, threshold) === ~ return positive - negative >= threshold === function route_diff_before(positive, negative, threshold) === ~ return positive - negative <= threshold === function route_diff_between(positive, negative, min, max) === ~ temp d = positive - negative ~ return d >= min && d <= max === function route_total() === ~ return route_composure + route_detective + route_lover + route_sapphic + route_careless + route_eccentric === function route_share_reached(value, numerator, denominator) === ~ temp total = route_total() ~ return total > 0 && value * denominator >= total * numerator === function route_share_before(value, numerator, denominator) === ~ temp total = route_total() ~ return total > 0 && value * denominator < total * numerator === function route_high(value) === ~ return route_share_reached(value, 9, 10) === function route_up(value) === ~ return route_share_reached(value, 7, 10) === function route_down(value) === ~ return route_share_before(value, 3, 10) === function route_low(value) === ~ return route_share_before(value, 1, 10) === function matrix_total(positive, negative) === ~ return positive + negative === function matrix_share_reached(positive, negative, numerator, denominator) === ~ temp total = matrix_total(positive, negative) ~ return total > 0 && positive * denominator >= total * numerator === function matrix_high(positive, negative) === ~ return matrix_share_reached(positive, negative, 9, 10) === function matrix_up(positive, negative) === ~ return matrix_share_reached(positive, negative, 7, 10) === function matrix_down(positive, negative) === ~ return matrix_share_reached(negative, positive, 7, 10) === function matrix_low(positive, negative) === ~ return matrix_share_reached(negative, positive, 9, 10) === function axis_positive(axis) === { - axis == order_axis: ~ return route_composure - axis == inquiry_axis: ~ return route_detective - axis == allure_axis: ~ return route_lover + route_sapphic - else: ~ return 0 } === function axis_negative(axis) === { - axis == order_axis: ~ return route_eccentric - axis == inquiry_axis: ~ return route_careless - axis == allure_axis: ~ return route_composure - else: ~ return 0 } === function axis_high(axis) === ~ return matrix_high(axis_positive(axis), axis_negative(axis)) === function axis_up(axis) === ~ return matrix_up(axis_positive(axis), axis_negative(axis)) === function axis_down(axis) === ~ return matrix_down(axis_positive(axis), axis_negative(axis)) === function axis_low(axis) === ~ return matrix_low(axis_positive(axis), axis_negative(axis)) // ============================================================================ // TIME-SLOT HELPERS // ============================================================================ // TimeSlot is an ordered LIST declared in Eibenreith.ink. // Use these helpers for the daily schedule structure. === function day(value) === ~ return current_day == value === function day_move_to(value) === ~ current_day = value === function day_inc() === ~ current_day += 1 === function time(slot) === ~ return current_slot == slot === function time_move_to(slot) === ~ current_slot = slot === function time_is(slot) === ~ return current_slot == slot === function time_before(slot) === ~ return current_slot < slot === function time_reached(slot) === ~ return current_slot >= slot === function time_between(first, last) === ~ return current_slot >= first && current_slot <= last // ============================================================================ // TIMETABLE / EPISODE DISPATCH // ============================================================================ // Content files define episode entry knots and buckets. Timetable control lives // here so authored episode files do not choose themselves. === start_game === -> run_slot(mid_morning) === run_slot(slot) === { - slot == mid_morning: -> run_episode(slot_mid_morning_episode) - slot == noon: -> run_episode(slot_noon_episode) - slot == afternoon: -> run_episode(slot_afternoon_episode) - slot == evening: -> run_episode(slot_evening_episode) - else: -> END } === function slot_episode(slot, episode_to_run) === { - slot == early_morning: ~ return slot_early_morning_episode == episode_to_run - slot == mid_morning: ~ return slot_mid_morning_episode == episode_to_run - slot == noon: ~ return slot_noon_episode == episode_to_run - slot == afternoon: ~ return slot_afternoon_episode == episode_to_run - slot == evening: ~ return slot_evening_episode == episode_to_run - slot == early_night: ~ return slot_early_night_episode == episode_to_run - slot == late_night: ~ return slot_late_night_episode == episode_to_run - else: ~ return false } === function slot_schedule(slot, episode_to_run) === { - slot == early_morning: ~ slot_early_morning_episode = episode_to_run - slot == mid_morning: ~ slot_mid_morning_episode = episode_to_run - slot == noon: ~ slot_noon_episode = episode_to_run - slot == afternoon: ~ slot_afternoon_episode = episode_to_run - slot == evening: ~ slot_evening_episode = episode_to_run - slot == early_night: ~ slot_early_night_episode = episode_to_run - slot == late_night: ~ slot_late_night_episode = episode_to_run } === run_episode(episode_to_run) === { - episode_to_run == episode_train_intro: -> enter_episode(episode_train_intro, mid_morning, -> train_intro_start, -> train_intro_end, -> train_intro_episode_bucket) -> train_intro_start - episode_to_run == episode_station_midday: -> enter_episode(episode_station_midday, noon, -> station_midday, -> station_midday_end, -> station_midday_episode_bucket) -> station_midday - episode_to_run == episode_carriage_ride: -> enter_episode(episode_carriage_ride, afternoon, -> carriage_ride, -> carriage_ride_end, -> carriage_ride_episode_bucket) -> carriage_ride - episode_to_run == episode_first_dinner: -> pre_dinner_transition - else: -> END } === advance_to_slot(slot) === ~ current_slot = slot -> run_slot(slot) // ============================================================================ // EPISODE HELPERS // ============================================================================ // EpisodeId is a LIST declared in Eibenreith.ink. // active_episode is the coarse structural episode currently being played. === function episode(value) === ~ return active_episode == value === function episode_start(value, slot) === ~ active_episode = value ~ current_slot = slot ~ last_slot_outcome = "normal" === function episode_move_to(value) === ~ active_episode = value === function episode_end(outcome) === ~ active_episode = no_episode ~ current_episode_bucket = -> empty_bucket ~ current_episode_start = -> empty_bucket ~ current_episode_end = -> empty_bucket ~ last_slot_outcome = outcome === function episode_active(value) === ~ return active_episode == value === function outcome_is(value) === ~ return last_slot_outcome == value === function outcome_move_to(value) === ~ last_slot_outcome = value // ============================================================================ // MEAL-PLAN HELPERS // ============================================================================ // MealPlan is a LIST declared in Eibenreith.ink. // The arrival day uses this to remember how lunch is handled. === function meal(value) === ~ return meal_plan == value === function meal_choose(value) === ~ meal_plan = value === function meal_is(value) === ~ return meal_plan == value === function meal_clear() === ~ meal_plan = meal_unset // ============================================================================ // LOCATION HELPERS // ============================================================================ // Location is a LIST declared in Eibenreith.ink. // current_location is intentionally coarse and exists so episode/game buckets // can react to where Valerie currently is. === function loc(value) === ~ return current_location == value === function loc_move_to(value) === ~ current_location = value ~ move_companions_to(value) === function loc_is(value) === ~ return current_location == value === function accompanied_by(character) === ~ return companions ? character === function companion_join(character) === ~ companions += character === function companion_leave(character) === ~ companions -= character === function present(character) === { - character == viktor: ~ return viktor_location == current_location - else: ~ return false } === function character_move_to(character, location) === { - character == viktor: ~ viktor_location = location } === function move_companions_to(location) === { - accompanied_by(viktor): ~ viktor_location = location } === enter_episode(value, slot, start_bucket, end_bucket, episode_bucket) === ~ episode_start(value, slot) ~ current_episode_start = start_bucket ~ current_episode_end = end_bucket ~ current_episode_bucket = episode_bucket ->-> === enter_room(location, entry_bucket, look_bucket, exit_bucket, moment_bucket) === ~ loc_move_to(location) ~ current_room_entry_bucket = entry_bucket ~ current_moment_bucket = moment_bucket ~ current_room_look_bucket = look_bucket ~ current_room_exit_bucket = exit_bucket ->-> === function room_seen_on_enter() === ~ return TURNS_SINCE(current_room_entry_bucket) >= 0 // ============================================================================ // GLOBAL STORY-STATE STORAGE // ============================================================================ // Chapter-specific state machines are declared as LISTs in the main file, // directly above the chapter index they belong to. // // story_state stores all reached states from all LIST-based state machines. // State functions below implement high-watermark logic: // reaching a later state automatically reaches all earlier states in the same // LIST chain. VAR story_state = () // ============================================================================ // STATE HELPERS // ============================================================================ // Remove and return the lowest item from a list. // Internal helper for state_reach(). === function state_pop(ref list) === ~ temp x = LIST_MIN(list) ~ list -= x ~ return x // Return true if the given forward state has been reached. // Use only with one-way encounter/progress LISTs advanced through state_reach(). // For independent checklist facts, use has() / lacks() / mark(). // Example: // {state_reached(CourtMission.hidden_instruction_revealed): ...} === function state_reached(state) === ~ return LIST_COUNT(story_state ^ state) > 0 // Return true if the given forward state has not yet been reached. // Use only with one-way encounter/progress LISTs advanced through state_reach(). // Example: // {state_before(CourtMission.hidden_instruction_revealed): ...} === function state_before(state) === ~ return not state_reached(state) // Return true if lower_state has been reached but upper_state has not. // Lower bound inclusive, upper bound exclusive. // Example: // {state_between(CourtMission.official_cover_understood, CourtMission.hidden_instruction_revealed): ...} === function state_between(lower_state, upper_state) === ~ return state_reached(lower_state) && not state_reached(upper_state) // Return true if state is the highest reached state in its LIST chain. // This is the practical equivalent of current-state testing for a high-watermark chain. === function state_is(state) === ~ temp chain = LIST_ALL(state) ~ temp reached_in_chain = story_state ^ chain { - reached_in_chain: ~ return LIST_MAX(reached_in_chain) == state - else: ~ return false } // Return the highest reached state in the same LIST chain as state. // Useful mainly for debugging or diagnostic text. === function state_current(state) === ~ temp chain = LIST_ALL(state) ~ temp reached_in_chain = story_state ^ chain { - reached_in_chain: ~ return LIST_MAX(reached_in_chain) - else: ~ return () } // 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. // // Examples: // ~ state_reach(CourtMission.hidden_instruction_revealed) // ~ state_reach((CourtMission.official_cover_understood, ViktorRelationFrame.handler_role_understood)) === function state_reach(states_to_set) === ~ temp x = state_pop(states_to_set) { - not x: ~ return false - not state_reached(x): ~ temp chain = LIST_ALL(x) ~ temp states_gained = LIST_RANGE(chain, LIST_MIN(chain), x) ~ story_state += states_gained ~ state_reach(states_to_set) ~ return true - else: ~ return false || state_reach(states_to_set) } // Return true if every given independent fact has been marked. // Facts are exact checklist items, not forward-moving encounter states. // Example: // {has(class_def)} // {has((class_def, name_def))} === function has(facts) === ~ return story_state ? facts // Return true if at least one given independent fact is still missing. // Example: // {lacks(relig_def)} // {lacks((body_def, face_def))} === function lacks(facts) === ~ return not has(facts) // Mark an independent fact, or a list of independent facts. // Unlike state_reach(), this does not imply earlier items in the same LIST. // Example: // ~ mark(class_def) // ~ mark((class_def, name_def)) === function mark(facts_to_set) === ~ story_state += facts_to_set ~ return true // Alias for readability when scene code wants to say move_to. // Same high-watermark behavior as state_reach(). === function state_move_to(state) === ~ state_reach(state) // Remove all reached states in the same LIST chain as state. // Use rarely: debug, dream logic, simulation reset, or deliberate memory loss. === function state_clear(state) === ~ story_state -= LIST_ALL(state) // Clear all reached states from all state machines. // Use only for debug or full reinitialization. === function state_clear_all() === ~ story_state = () // ============================================================================ // RELATIONSHIP HELPERS // ============================================================================ // Relationship variables are defined in eibenreith_00_character_relationships.ink. // These helpers operate on those variables by reference. // // Ink does not support overloaded functions or optional parameters. // Therefore: // - rel_inc(rel) increases by 1 // - rel_inc_by(rel, amount) increases by amount === function rel(value) === ~ return value === function rel_inc(ref value) === ~ value += 1 === function rel_inc_by(ref value, amount) === ~ value += amount === function rel_dec(ref value) === ~ value -= 1 === function rel_dec_by(ref value, amount) === ~ value -= amount === function rel_move_to(ref value, amount) === ~ value = amount === function rel_clear(ref value) === ~ value = 0 === function rel_is(value, amount) === ~ return value == amount === function rel_before(value, amount) === ~ return value <= amount === function rel_reached(value, amount) === ~ return value >= amount === function rel_between(value, min, max) === ~ return value >= min && value <= max === function rel_min(a, b) === { - a <= b: ~ return a - else: ~ return b } === function rel_max(a, b) === { - a >= b: ~ return a - else: ~ return b } === function rel_diff(positive, negative) === ~ return positive - negative === function rel_diff_reached(positive, negative, threshold) === ~ return positive - negative >= threshold === function rel_diff_before(positive, negative, threshold) === ~ return positive - negative <= threshold === function rel_diff_between(positive, negative, min, max) === ~ temp d = positive - negative ~ return d >= min && d <= max === function rel_share_reached(value, total, numerator, denominator) === ~ return total > 0 && value * denominator >= total * numerator === function rel_high(positive, negative) === ~ return matrix_high(positive, negative) === function rel_up(positive, negative) === ~ return matrix_up(positive, negative) === function rel_down(positive, negative) === ~ return matrix_down(positive, negative) === function rel_low(positive, negative) === ~ return matrix_low(positive, negative)