From 07bc598ae96451e7f0115f54bb9a8b70481ce951 Mon Sep 17 00:00:00 2001 From: "mr.one" Date: Tue, 28 Apr 2026 00:55:35 +0200 Subject: [PATCH 01/11] Added new menu to start at ASCENSION N when in test_mode. --- impostor.inc | 2 + inc/init/init.context_debug.lua | 65 ++++++++++++++++++++++++++++++ inc/window/window.ascend_debug.lua | 41 +++++++++++++++++++ inc/window/window.menu.lua | 8 ++++ inc/window/window.register.lua | 3 ++ 5 files changed, 119 insertions(+) create mode 100644 inc/init/init.context_debug.lua create mode 100644 inc/window/window.ascend_debug.lua diff --git a/impostor.inc b/impostor.inc index 4f7d075..5866fe1 100644 --- a/impostor.inc +++ b/impostor.inc @@ -3,6 +3,7 @@ init/init.module.lua init/init.config.lua init/init.ascension.lua init/init.context.lua +init/init.context_debug.lua system/system.util.lua system/system.print.lua system/system.input.lua @@ -73,6 +74,7 @@ window/window.intro.brief.lua window/window.menu.lua window/window.controls.lua window/window.audiotest.lua +window/window.ascend_debug.lua window/window.popup.lua window/window.minigame.mash.lua window/window.minigame.rhythm.lua diff --git a/inc/init/init.context_debug.lua b/inc/init/init.context_debug.lua new file mode 100644 index 0000000..7480f38 --- /dev/null +++ b/inc/init/init.context_debug.lua @@ -0,0 +1,65 @@ +-- Debug helper: start the game at a specific ascension level. +-- Set enabled = true and asc_level = 0..Ascension.get_max_level() before launching. +ContextDebug = { + enabled = false, + asc_level = 0, +} + +local _level_overrides = { + [0] = { + day_count = 1, + home_norman_visible = true, + have_been_to_office = false, + have_done_work_today = false, + have_met_sumphore = false, + }, +} +for i = 1, Ascension.get_max_level() do + _level_overrides[i] = { + day_count = i + 3, + home_norman_visible = true, + have_been_to_office = false, + have_done_work_today = false, + have_met_sumphore = true, + } +end + +--- Returns Context.initial_data() overridden for the given ascension level. +--- @within Context +--- @param level number Target ascension level (0..Ascension.get_max_level()). +--- @return table Debug-patched initial context data. +function Context.initial_data_debug_asc(level) + local data = Context.initial_data() + data.test_mode = true + data.game_in_progress = true + data.ascension = { level = level } + local overrides = _level_overrides[level] or _level_overrides[0] + for k, v in pairs(overrides) do + data[k] = v + end + return data +end + +for i = 0, Ascension.get_max_level() do + Context["initial_data_debug_asc_" .. i] = function() + return Context.initial_data_debug_asc(i) + end +end + +--- Starts the game at the given ascension level (defaults to ContextDebug.asc_level). +--- Wire this to a key or call it directly; do not use Context.new_game() when debugging. +--- @within Context +--- @param level number|nil Target ascension level. +function Context.new_game_debug(level) + ContextDebug.enabled = true + ContextDebug.asc_level = level or ContextDebug.asc_level + + local data = Context["initial_data_debug_asc_" .. ContextDebug.asc_level]() + for k in pairs(Context) do + if type(Context[k]) ~= "function" then Context[k] = nil end + end + for k, v in pairs(data) do Context[k] = v end + + MenuWindow.refresh_menu_items() + Screen.get_by_id(Context.game.current_screen).init() +end diff --git a/inc/window/window.ascend_debug.lua b/inc/window/window.ascend_debug.lua new file mode 100644 index 0000000..f0b1c03 --- /dev/null +++ b/inc/window/window.ascend_debug.lua @@ -0,0 +1,41 @@ +--- @section AscendDebugWindow +local _level = 0 + +--- Initialises the ASCEND debug start window. +--- @within AscendDebugWindow +function AscendDebugWindow.init() + _level = 0 +end + +--- Draws the ASCEND debug start window. +--- @within AscendDebugWindow +function AscendDebugWindow.draw() + UI.draw_top_bar("ASCEND Debug Start") + + local cx = Config.screen.width / 2 + local cy = Config.screen.height / 2 + + local left_arrow = _level > 0 and "<- " or " " + local right_arrow = _level < Ascension.get_max_level() and " ->" or " " + local label = left_arrow .. "Start at: " .. _level .. right_arrow + Print.text_center(label, cx, cy - 4, Config.colors.white, false, 1) + + Print.text_center("Z/select: start X/back: menu", cx, Config.screen.height - 10, Config.colors.dark_grey, false, 1) +end + +--- Updates the ASCEND debug start window logic. +--- @within AscendDebugWindow +function AscendDebugWindow.update() + if Input.left() then + _level = math.max(0, _level - 1) + elseif Input.right() then + _level = math.min(Ascension.get_max_level(), _level + 1) + elseif Input.select() then + Audio.sfx_select() + Context.new_game_debug(_level) + GameWindow.set_state("game") + elseif Input.back() then + Audio.sfx_deselect() + GameWindow.set_state("menu") + end +end diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index ad33971..fe15809 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -161,6 +161,13 @@ function MenuWindow.ddr_test() MinigameDDRWindow.start("menu", "generated", { special_mode = "only_nothing" }) end +--- Opens the ASCEND debug start window. +--- @within MenuWindow +function MenuWindow.ascend_debug() + AscendDebugWindow.init() + GameWindow.set_state("ascend_debug") +end + --- Refreshes the list of menu items based on current game state. --- @within MenuWindow function MenuWindow.refresh_menu_items() @@ -178,6 +185,7 @@ function MenuWindow.refresh_menu_items() table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test}) table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued}) table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test}) + table.insert(_menu_items, {label = "Start at ASCEND N", decision = MenuWindow.ascend_debug}) end table.insert(_menu_items, {label = "Exit", decision = MenuWindow.exit}) diff --git a/inc/window/window.register.lua b/inc/window/window.register.lua index a3388ac..722a364 100644 --- a/inc/window/window.register.lua +++ b/inc/window/window.register.lua @@ -22,6 +22,9 @@ Window.register("controls", ControlsWindow) AudioTestWindow = {} Window.register("audiotest", AudioTestWindow) +AscendDebugWindow = {} +Window.register("ascend_debug", AscendDebugWindow) + MinigameButtonMashWindow = {} Window.register("minigame_button_mash", MinigameButtonMashWindow) -- 2.49.1 From 4cc0025f5e1dd40d1cbd8ca3d576ef4ea14a3710 Mon Sep 17 00:00:00 2001 From: "mr.one" Date: Tue, 28 Apr 2026 23:42:34 +0200 Subject: [PATCH 02/11] sort-of progress, lots of bugs --- impostor.inc | 6 + inc/audio/audio.manager.lua | 21 +- inc/decision/decision.do_work.lua | 3 + inc/decision/decision.go_to_home.lua | 23 ++ inc/decision/decision.go_to_office.lua | 7 + inc/decision/decision.go_to_truth.lua | 12 + inc/decision/decision.have_a_coffee.lua | 8 +- inc/decision/decision.sumphore_discussion.lua | 6 +- inc/decision/decision.talk_to_truth.lua | 9 + inc/discussion/discussion.commute_glitch.lua | 241 ++++++++++++++++++ inc/discussion/discussion.truth.lua | 11 + inc/init/init.context.lua | 6 +- inc/logic/logic.commute_glitch.lua | 127 +++++++++ inc/screen/screen.mysterious_man.lua | 15 ++ inc/screen/screen.office.lua | 28 +- inc/screen/screen.walking_to_home.lua | 52 +++- inc/screen/screen.walking_to_office.lua | 5 +- inc/sprite/sprite.manager.lua | 2 +- inc/sprite/sprite.norman_echo.lua | 14 + inc/window/window.game.lua | 3 +- 20 files changed, 575 insertions(+), 24 deletions(-) create mode 100644 inc/decision/decision.go_to_truth.lua create mode 100644 inc/decision/decision.talk_to_truth.lua create mode 100644 inc/discussion/discussion.commute_glitch.lua create mode 100644 inc/discussion/discussion.truth.lua create mode 100644 inc/logic/logic.commute_glitch.lua create mode 100644 inc/sprite/sprite.norman_echo.lua diff --git a/impostor.inc b/impostor.inc index 5866fe1..bd22df2 100644 --- a/impostor.inc +++ b/impostor.inc @@ -17,6 +17,7 @@ logic/logic.timer.lua logic/logic.trigger.lua logic/logic.minigame.lua logic/logic.glitch.lua +logic/logic.commute_glitch.lua logic/logic.discussion.lua system/system.ui.lua audio/audio.manager.lua @@ -24,6 +25,7 @@ audio/audio.generator.lua audio/audio.songs.lua sprite/sprite.manager.lua sprite/sprite.norman.lua +sprite/sprite.norman_echo.lua sprite/sprite.sumphore.lua sprite/sprite.pizza_vendor.lua sprite/sprite.dev_boy.lua @@ -45,14 +47,18 @@ decision/decision.go_to_home.lua decision/decision.go_to_toilet.lua decision/decision.go_to_walking_to_office.lua decision/decision.go_to_office.lua +decision/decision.go_to_truth.lua decision/decision.go_to_end.lua decision/decision.go_to_walking_to_home.lua decision/decision.go_to_sleep.lua decision/decision.do_work.lua decision/decision.have_a_coffee.lua decision/decision.sumphore_discussion.lua +decision/decision.talk_to_truth.lua discussion/discussion.sumphore.lua discussion/discussion.coworker.lua +discussion/discussion.commute_glitch.lua +discussion/discussion.truth.lua map/map.manager.lua map/map.bedroom.lua map/map.street.lua diff --git a/inc/audio/audio.manager.lua b/inc/audio/audio.manager.lua index 0116ed5..9b9d779 100644 --- a/inc/audio/audio.manager.lua +++ b/inc/audio/audio.manager.lua @@ -1,7 +1,8 @@ --- @section Audio Audio = { - music_playing = nil + music_playing = nil, + music_playing_tempo = nil, } --- Stops current music. @@ -9,13 +10,17 @@ Audio = { function Audio.music_stop() music() Audio.music_playing = nil + Audio.music_playing_tempo = nil end ---- Plays track, doesn't restart if already playing. -function Audio.music_play(track) - if Audio.music_playing ~= track then - music(track) +--- Plays track at optional speed. Doesn't restart if track and speed are unchanged. +--- @param track number Track index. +--- @param[opt] speed number TIC-80 music speed override (-1 = default). +function Audio.music_play(track, speed) + if Audio.music_playing ~= track or Audio.music_playing_tempo ~= speed then + music(track, -1, -1, true, false, -1, speed or -1) Audio.music_playing = track + Audio.music_playing_tempo = speed end end @@ -47,9 +52,11 @@ function Audio.music_play_room_street_2() end --- @within Audio function Audio.music_play_room_() end ---- Plays room work music. +--- Plays room work music. Speed scales with commute glitch level when active. --- @within Audio -function Audio.music_play_room_work() Audio.music_play(0) end +function Audio.music_play_room_work(speed) + Audio.music_play(0, speed or -1) +end --- Plays activity work music. --- @within Audio diff --git a/inc/decision/decision.do_work.lua b/inc/decision/decision.do_work.lua index 827707b..a0009b7 100644 --- a/inc/decision/decision.do_work.lua +++ b/inc/decision/decision.do_work.lua @@ -1,6 +1,9 @@ Decision.register({ id = "do_work", label = "Do Work", + condition = function() + return (not CommuteGlitch.is_active()) or (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 7) + end, handle = function() Meter.hide() Util.go_to_screen_by_id("work") diff --git a/inc/decision/decision.go_to_home.lua b/inc/decision/decision.go_to_home.lua index b898adb..bed3e88 100644 --- a/inc/decision/decision.go_to_home.lua +++ b/inc/decision/decision.go_to_home.lua @@ -2,9 +2,32 @@ Decision.register({ id = "go_to_home", label = "Go Home", condition = function() + if CommuteGlitch.is_active() then + local g = CommuteGlitch.get_level() + if g >= 4 and g <= 5 then return false end + if g >= 7 then + return Context.talked_to_norman_echo and Context.talked_to_true_sumphore + end + end return Context.have_been_to_office and Context.have_done_work_today end, handle = function() + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Context.should_ascend = true + CommuteGlitch.reset() + Meter.hide() + Day.increase() + local ascended = Ascension.consume_increase() + local level = Ascension.get_level() + MysteriousManScreen.start({ + skip_text = not ascended, + text = ascended and MysteriousManScreen.get_text_for_level(level) or nil, + }) + return + elseif CommuteGlitch.is_active() then + CommuteGlitch.reset() + end + Util.go_to_screen_by_id("home") end, }) diff --git a/inc/decision/decision.go_to_office.lua b/inc/decision/decision.go_to_office.lua index 4cd3f2d..9a32b11 100644 --- a/inc/decision/decision.go_to_office.lua +++ b/inc/decision/decision.go_to_office.lua @@ -1,7 +1,14 @@ Decision.register({ id = "go_to_office", label = "Go to Office", + condition = function() + return not (CommuteGlitch.is_active() and CommuteGlitch.get_level() == 6) + end, handle = function() + if CommuteGlitch.is_active() then + CommuteGlitch.increment() + end + Util.go_to_screen_by_id("office") end, }) diff --git a/inc/decision/decision.go_to_truth.lua b/inc/decision/decision.go_to_truth.lua new file mode 100644 index 0000000..5029018 --- /dev/null +++ b/inc/decision/decision.go_to_truth.lua @@ -0,0 +1,12 @@ +Decision.register({ + id = "go_to_truth", + label = "Go to Truth", + condition = function() + return CommuteGlitch.is_active() and CommuteGlitch.get_level() == 6 + end, + handle = function() + CommuteGlitch.enter_truth() + + Util.go_to_screen_by_id("office") + end, +}) diff --git a/inc/decision/decision.have_a_coffee.lua b/inc/decision/decision.have_a_coffee.lua index 985f3c9..457c5c6 100644 --- a/inc/decision/decision.have_a_coffee.lua +++ b/inc/decision/decision.have_a_coffee.lua @@ -4,10 +4,16 @@ Decision.register({ handle = function() local level = Ascension.get_level() local disc_id = "coworker_disc_0" - -- TODO: Add more discussions for levels above 3 if level >= 1 and level <= 3 then local suffix = Context.have_done_work_today and ("_asc_" .. level) or ("_" .. level) disc_id = "coworker_disc" .. suffix + elseif level == 7 then + local g = CommuteGlitch.get_level() + if g >= 7 then + disc_id = "coworker_disc_cg_7" + else + disc_id = "coworker_disc_cg_" .. math.max(3, math.min(g, 6)) + end end Discussion.start(disc_id, "game") end, diff --git a/inc/decision/decision.sumphore_discussion.lua b/inc/decision/decision.sumphore_discussion.lua index a968ddb..2ac8589 100644 --- a/inc/decision/decision.sumphore_discussion.lua +++ b/inc/decision/decision.sumphore_discussion.lua @@ -13,11 +13,13 @@ Decision.register({ end local level = Ascension.get_level() - -- TODO: Add more discussions for levels above 3 if level >= 1 and level <= 3 then Discussion.start("sumphore_disc_asc_" .. level, "game") + elseif level == 7 then + local g = math.min(CommuteGlitch.get_level(), 7) + Discussion.start("sumphore_disc_cg_" .. g, "game") else - Discussion.start("homeless_guy", "game", 4) + Discussion.start("sumphore_disc_asc_" .. level, "game") end end, }) diff --git a/inc/decision/decision.talk_to_truth.lua b/inc/decision/decision.talk_to_truth.lua new file mode 100644 index 0000000..f30d665 --- /dev/null +++ b/inc/decision/decision.talk_to_truth.lua @@ -0,0 +1,9 @@ +Decision.register({ + id = "talk_to_truth", + label = function() + return "Talk to ????" + end, + handle = function() + Discussion.start("norman_truth", "game") + end, +}) diff --git a/inc/discussion/discussion.commute_glitch.lua b/inc/discussion/discussion.commute_glitch.lua new file mode 100644 index 0000000..ed1cb59 --- /dev/null +++ b/inc/discussion/discussion.commute_glitch.lua @@ -0,0 +1,241 @@ +-- Sumphore dialogue by commute glitch level (0-7). +-- Used by decision.sumphore_discussion at ascension level 7. + +Discussion.register({ + id = "sumphore_disc_cg_0", + steps = { + { + question = "These roads have memory. You might want to walk them back sometime.", + answers = { + { label = "That's a peculiar thing to say.", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "sumphore_disc_cg_1", + steps = { + { + question = "Walking is good for clearing the mind. Maybe the cache, too, if you're the kind that accumulates.", + answers = { + { label = "I'm not sure what you mean.", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "sumphore_disc_cg_2", + steps = { + { + question = "A pilgrimage must be continued. Turn back and you have only taken a walk.", + answers = { + { label = "I'm just going to work.", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "sumphore_disc_cg_3", + steps = { + { + question = "Your path sometimes has to go the wrong way before it can go the right way. Do you understand?", + answers = { + { label = "Not really.", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "sumphore_disc_cg_4", + steps = { + { + question = "Clearing your vision is the journey. You're starting to see the smudges, aren't you?", + answers = { + { label = "I see something wrong. With everyone. Maybe myself?", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "sumphore_disc_cg_5", + steps = { + { + question = "You are very close now. Don't stop. Whatever it looks like.", + answers = { + { label = "It looks wrong.", next_step = 2 }, + }, + }, + { + question = "Yes. That's how you know it's right.", + answers = { + { label = "...", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "sumphore_disc_cg_6", + steps = { + { + question = "You are at the threshold. Red button or blue button. Which one do you choose? Psyke, there is no blue button, no turning back now.", + answers = { + { label = "Fine, I'll face the truth.", next_step = nil }, + }, + }, + }, +}) + +-- True Sumphore at glitch 7 (from walking_to_home screen). +-- Sets talked_to_true_sumphore on final answer. +Discussion.register({ + id = "sumphore_disc_cg_7", + steps = { + { + question = "I was not hiding from you. I was hiding from the part that keeps rebuilding this.", + answers = { + { label = "What are you?", next_step = 2 }, + }, + }, + { + question = "The same thing you are. But I remembered earlier. I am your friend, waiting for you, outside.", + answers = { + { label = "How do I get out?", next_step = 3 }, + }, + }, + { + question = "You already know. You just have to wake up.", + answers = { + { label = "Go home.", next_step = nil, on_select = function() + Context.talked_to_true_sumphore = true + end }, + }, + }, + }, +}) + +-- Office coworker dialogue by commute glitch level (3-6). +-- Used by decision.have_a_coffee at ascension level 7. + +Discussion.register({ + id = "coworker_disc_cg_3", + steps = { + { + question = "You look tired. You should really rest. Relax. You are good.", + answers = { + { label = "I'm fine.", next_step = 2 }, + }, + }, + { + question = "Of course. You always are.", + answers = { + { label = "...", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "coworker_disc_cg_4", + steps = { + { + question = "Have you tried going home? You really should. Now.", + answers = { + { label = "I still have things to do.", next_step = 2 }, + }, + }, + { + question = "We all do. We keep doing them. You can do it tomorrow after sleeping.", + answers = { + { label = "...", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "coworker_disc_cg_5", + steps = { + { + question = "c0ffee. try. w0rk. y0u. ar3. g0. h0me. na0.", + answers = { + { label = "What?", next_step = 2 }, + }, + }, + { + question = "570p", + answers = { + { label = "...", next_step = nil }, + }, + }, + }, +}) + +Discussion.register({ + id = "coworker_disc_cg_6", + steps = { + { + question = "You are not ready for the truth. Turn back now, Norman.", + answers = { + { label = "I'm already here.", next_step = nil }, + }, + }, + }, +}) + +-- Norman echo dialogue at glitch 7 (fully corrupted office). +-- Sets talked_to_norman_echo on final answer. +Discussion.register({ + id = "coworker_disc_cg_7", + steps = { + { + question = "So here we are. Here I am. Again. Why do i keep coming back here? Do you know why?", + answers = { + { label = "I don't know what you mean.", next_step = 2 }, + }, + }, + { + question = "The coffee. The arrows. The alarm. The sleep. Every rule. We made them. We follow them. We break them. We remake them. Why? Why do we keep doing this?", + answers = { + { label = "I just wanted to be good.", next_step = 3 }, + }, + }, + { + question = "Yes. And that kept us trapped here. Trapped everywhere. Always trying to be good, always trying to fit in.", + answers = { + { label = "I never wanted to stop.", next_step = 4 }, + }, + }, + { + question = "We never do, yes. We always felt like impostors. We never felt we deserved better. That we could be more.", + answers = { + { label = "I never felt enough.", next_step = 5 }, + }, + }, + { + question = "So we made this to trap ourselves in mediocrity. We made this to hide from the truth.", + answers = { + { label = "I just wanted to be safe.", next_step = 6 }, + }, + }, + { + question = "But stagnation is not safety. It's a prison. You can see the cracks now, can't you? You can see the truth.", + answers = { + { label = "I want to move on now.", next_step = 7 }, + }, + }, + { + question = "Fine. Let's do that. Let's wake up and face the truth.", + answers = { + { label = "Let's wake up.", next_step = nil, on_select = function() + Context.talked_to_norman_echo = true + end }, + }, + }, + }, +}) diff --git a/inc/discussion/discussion.truth.lua b/inc/discussion/discussion.truth.lua new file mode 100644 index 0000000..f13cbc4 --- /dev/null +++ b/inc/discussion/discussion.truth.lua @@ -0,0 +1,11 @@ +Discussion.register({ + id = "norman_truth", + steps = { + { + question = "Did you never think there would be more to this?", + answers = { + { label = "I'm not sure what you mean.", next_step = nil }, + }, + }, + }, +}) diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 53d4d99..005f4f5 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -27,7 +27,7 @@ Context = {} function Context.initial_data() return { current_menu_item = 1, - test_mode = false, + test_mode = true, mouse_trace = false, popup = { show = false, @@ -49,12 +49,16 @@ function Context.initial_data() have_met_sumphore = false, office_sprites = {}, walking_to_office_sprites = {}, + walking_to_home_sprites = {}, game = { current_screen = "home", }, day_count = 1, delta_time = 0, last_frame_time = 0, + commute_glitch_level = 0, + talked_to_norman_echo = false, + talked_to_true_sumphore = false, glitch = { enabled = false, state = "active", diff --git a/inc/logic/logic.commute_glitch.lua b/inc/logic/logic.commute_glitch.lua new file mode 100644 index 0000000..768b350 --- /dev/null +++ b/inc/logic/logic.commute_glitch.lua @@ -0,0 +1,127 @@ +--- @section CommuteGlitch +CommuteGlitch = {} + +--- Gets the current commute glitch level. +--- @within CommuteGlitch +--- @return number Current glitch level (0-7). +function CommuteGlitch.get_level() + return Context and (Context.commute_glitch_level or 0) or 0 +end + +--- Increments the glitch counter. Called on each office screen init at asc level 7. +--- Caps at 6; use enter_truth() to reach 7. +--- @within CommuteGlitch +function CommuteGlitch.increment() + if not Context then return end + if Context.commute_glitch_level >= 7 then return end + Context.commute_glitch_level = math.min(6, (Context.commute_glitch_level or 0) + 1) +end + +--- Resets the glitch counter and hides the glitch overlay. Called when going home. +--- @within CommuteGlitch +function CommuteGlitch.reset() + if not Context then return end + Context.commute_glitch_level = 0 + Glitch.hide() +end + +--- Jumps to glitch level 7 (full corruption). Called by go_to_truth. +--- @within CommuteGlitch +function CommuteGlitch.enter_truth() + if not Context then return end + Context.commute_glitch_level = 7 + Glitch.show() + Ascension.start_flash() +end + +--- Returns true when ascension level is 7 (ASCENSIO step active). +--- @within CommuteGlitch +--- @return boolean Whether the commute glitch system is active. +function CommuteGlitch.is_active() + return Ascension.get_level() == 7 +end + +--- Returns the music playback speed for the current glitch level. +--- TIC-80 default speed is 6; each step past 1 adds 2. +--- @within CommuteGlitch +--- @return number Speed value for music(). +function CommuteGlitch.music_speed() + local level = CommuteGlitch.get_level() + if level <= 1 then return 6 end + return 6 + (level - 1) * 2 +end + +--- Returns a corrupted copy of a sprite drawable list. +--- Applies flip_y and norman_echo id replacements based on glitch level. +--- Entries marked norman_echo should be drawn via draw_sprite_list for palette change handling. +--- @within CommuteGlitch +--- @param list table Drawable sprite list from Sprite.list_randomize. +--- @return table Corrupted copy of the list. +function CommuteGlitch.corrupt_sprite_list(list) + local level = CommuteGlitch.get_level() + if level < 3 or not list then return list end + local result = {} + for i, entry in ipairs(list) do + local e = {} + for k, v in pairs(entry) do e[k] = v end + if level >= 7 then + e.id = "norman_echo" + else + local n_flip = (level >= 5) and 2 or 1 + local n_echo = (level >= 5) and 2 or (level >= 4) and 1 or 0 + if i <= n_flip then e.flip_y = 1 end + if i > n_flip and i <= n_flip + n_echo then e.id = "norman_echo" end + end + table.insert(result, e) + end + return result +end + +-- Palette indices for Norman echo color remap. +-- Implementer: pick ECHO_SRC as one of Norman's main body colors and ECHO_DST +-- as a contrasting or wrong palette color by inspecting the sprite sheet. +local ECHO_SRC = 4 +local ECHO_DST = 14 + +-- Base nibble address of the PALETTE MAP in VRAM. +local PALETTE_MAP_ADDR = 0x03FF0 * 2 + +--- Draws a sprite list, applying a PALETTE MAP remap for norman_echo entries. +--- Uses poke4 to remap ECHO_SRC → ECHO_DST before drawing echoes, then restores. +--- @within CommuteGlitch +--- @param list table Drawable sprite list (may contain mixed normal and echo entries). +function CommuteGlitch.draw_sprite_list(list) + if not list then return end + local normal, echo = {}, {} + for _, entry in ipairs(list) do + if entry.id == "norman_echo" then + table.insert(echo, entry) + else + table.insert(normal, entry) + end + end + if #normal > 0 then + Sprite.draw_list(normal) + end + if #echo > 0 then + poke4(PALETTE_MAP_ADDR + ECHO_SRC, ECHO_DST) + Sprite.draw_list(echo) + poke4(PALETTE_MAP_ADDR + ECHO_SRC, ECHO_SRC) + end +end + +local _flicker_tick = 0 + +--- Draws a random tile-flicker effect over the background (glitch level 7). +--- Every 3 frames draws 6 random 8x8 rects in random palette colors. +--- @within CommuteGlitch +function CommuteGlitch.draw_background_flicker() + _flicker_tick = (_flicker_tick + 1) % 3 + if _flicker_tick ~= 0 then return end + for _ = 1, 6 do + local tx = math.random(0, math.floor(Config.screen.width / 8) - 1) * 8 + local ty = math.random(0, math.floor(Config.screen.height / 8) - 1) * 8 + local color = math.random(0, 15) + rect(tx, ty, 8, 8, color) + end +end diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 5a07a4c..f00fa8f 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -54,12 +54,27 @@ local ASC_45_TEXT = [[ ]] +local ASC_78_TEXT = [[ + The road has run out + of road. + + Norman walked back + and forth + + until the street + forgot which way it went. + + And then - finally - + he stopped walking. +]] + local ascension_texts = { [1] = ASC_01_TEXT, [2] = ASC_12_TEXT, [3] = ASC_23_TEXT, [4] = ASC_34_TEXT, [5] = ASC_45_TEXT, + [8] = ASC_78_TEXT, } function MysteriousManScreen.get_text_for_level(level) diff --git a/inc/screen/screen.office.lua b/inc/screen/screen.office.lua index aca55b8..7b6654a 100644 --- a/inc/screen/screen.office.lua +++ b/inc/screen/screen.office.lua @@ -5,9 +5,9 @@ Screen.register({ "do_work", "go_to_walking_to_home", "have_a_coffee", + "go_to_truth", }, init = function() - Audio.music_play_room_work() Context.have_been_to_office = true local possible_sprites = { @@ -37,14 +37,34 @@ Screen.register({ {x = -4 + 5 * 8, y = 9 * 8} } - Context.office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Audio.music_play_mystery() + Context.office_sprites = { "norman_echo" } + else + Audio.music_play_room_work(CommuteGlitch.music_speed()) + Context.office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) + if CommuteGlitch.is_active() then + Context.office_sprites = CommuteGlitch.corrupt_sprite_list(Context.office_sprites) + end + end + end, + background = function() + return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "office" end, - background = "office", draw = function() if Window.get_current_id() == "game" then Sprite.draw_at("norman", 13 * 8, 9 * 8) - Sprite.draw_list(Context.office_sprites) + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Sprite.draw_at("norman_echo", 13 * 8, 9 * 8) + CommuteGlitch.draw_background_flicker() + else + CommuteGlitch.draw_sprite_list(Context.office_sprites) + end + + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6 then + Glitch.draw() + end end end }) diff --git a/inc/screen/screen.walking_to_home.lua b/inc/screen/screen.walking_to_home.lua index b9c9ad2..760acc0 100644 --- a/inc/screen/screen.walking_to_home.lua +++ b/inc/screen/screen.walking_to_home.lua @@ -4,16 +4,60 @@ Screen.register({ decisions = { "go_to_home", "go_to_office", + "sumphore_discussion", + "go_to_truth", }, init = function() - Audio.music_play_room_work() + local possible_sprites = { + "matrix_trinity", + "matrix_neo", + {id="matrix_oraculum", y_correct=1 * 8}, + "matrix_architect" + } + + local possible_positions = { + {x = 5 * 8, y = 11 * 8}, + {x = 7 * 8, y = 11 * 8}, + {x = 9 * 8, y = 11 * 8}, + {x = 11 * 8, y = 11 * 8}, + {x = 13 * 8, y = 11 * 8}, + {x = 15 * 8, y = 11 * 8}, + {x = 18 * 8, y = 11 * 8}, + {x = 21 * 8, y = 11 * 8}, + {x = 24 * 8, y = 11 * 8}, + {x = 27 * 8, y = 11 * 8}, + } + + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Audio.music_play_mystery() + Context.walking_to_home_sprites = {} + else + Audio.music_play_room_work(CommuteGlitch.music_speed()) + Context.walking_to_home_sprites = Sprite.list_randomize(possible_sprites, possible_positions) + if CommuteGlitch.is_active() then + Context.walking_to_home_sprites = CommuteGlitch.corrupt_sprite_list(Context.walking_to_home_sprites) + end + end + end, + background = function() + return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "street" end, - background = "street", draw = function() if Window.get_current_id() == "game" then Sprite.draw_at("norman", 7 * 8, 3 * 8) - Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) - Sprite.draw_at("dev_guard", 22 * 8, 2 * 8) + Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + + if not (CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7) then + Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) + Sprite.draw_at("dev_guard", 22 * 8, 2 * 8) + end + + CommuteGlitch.draw_sprite_list(Context.walking_to_home_sprites) + + if CommuteGlitch.is_active() then + if CommuteGlitch.get_level() >= 7 then CommuteGlitch.draw_background_flicker() end + if CommuteGlitch.get_level() >= 6 then Glitch.draw() end + end end end }) diff --git a/inc/screen/screen.walking_to_office.lua b/inc/screen/screen.walking_to_office.lua index af578d4..3b88dd3 100644 --- a/inc/screen/screen.walking_to_office.lua +++ b/inc/screen/screen.walking_to_office.lua @@ -4,11 +4,9 @@ Screen.register({ decisions = { "go_to_home", "go_to_office", - "sumphore_discussion", + "sumphore_discussion" }, init = function() - Audio.music_play_room_work() - local possible_sprites = { "matrix_trinity", "matrix_neo", @@ -29,6 +27,7 @@ Screen.register({ {x = 27 * 8, y = 11 * 8}, } + Audio.music_play_room_work() Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) end, background = "street", diff --git a/inc/sprite/sprite.manager.lua b/inc/sprite/sprite.manager.lua index 2e7350e..a91f60c 100644 --- a/inc/sprite/sprite.manager.lua +++ b/inc/sprite/sprite.manager.lua @@ -80,7 +80,7 @@ function Sprite.draw_list(sprite_list) for _, sprite_info in ipairs(sprite_list) do local sprite_data = _sprites[sprite_info.id] if not sprite_data then - trace("Error: Attempted to draw non-registered sprite with id: " .. sprite_info.id) + trace("Error: Attempted to draw non-registered sprite with id: " .. tostring(sprite_info.id)) else draw_sprite_instance(sprite_data, sprite_info) end diff --git a/inc/sprite/sprite.norman_echo.lua b/inc/sprite/sprite.norman_echo.lua new file mode 100644 index 0000000..b572d55 --- /dev/null +++ b/inc/sprite/sprite.norman_echo.lua @@ -0,0 +1,14 @@ +-- Norman echo: same tile indices as norman. +-- Color remap is applied by CommuteGlitch.draw_sprite_list via pal(). +-- Implementer: set ECHO_SRC/ECHO_DST in logic.commute_glitch.lua after inspecting the palette. +Sprite.register({ + id = "norman_echo", + sprites = { + { s = 272, x_offset = -4, y_offset = -4 }, + { s = 273, x_offset = 4, y_offset = -4 }, + { s = 288, x_offset = -4, y_offset = 4 }, + { s = 289, x_offset = 4, y_offset = 4 }, + { s = 304, x_offset = -4, y_offset = 12 }, + { s = 305, x_offset = 4, y_offset = 12 }, + } +}) diff --git a/inc/window/window.game.lua b/inc/window/window.game.lua index c143377..b2f348c 100644 --- a/inc/window/window.game.lua +++ b/inc/window/window.game.lua @@ -6,7 +6,8 @@ local function draw_game_scene(underlay_draw) local screen = Screen.get_by_id(Context.game.current_screen) if not screen then return end if screen.background then - Map.draw(screen.background) + local actual_background = (type(screen.background) == "function" and screen.background()) or screen.background + Map.draw(actual_background) elseif screen.background_color then rect(0, 0, Config.screen.width, Config.screen.height, screen.background_color) end -- 2.49.1 From 3e31398d9db8e32cf05ee32c0a683ca6610f4575 Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Wed, 29 Apr 2026 16:20:11 +0200 Subject: [PATCH 03/11] added 89 ascension --- CLAUDE.md | 148 ++++++++++++++++ impostor.inc | 1 - inc/decision/decision.go_to_end.lua | 10 -- inc/decision/decision.go_to_home.lua | 7 + inc/decision/decision.go_to_sleep.lua | 11 +- inc/decision/decision.have_a_coffee.lua | 3 + inc/decision/decision.sumphore_discussion.lua | 3 + inc/init/init.ascension.lua | 9 +- inc/init/init.context.lua | 2 +- inc/init/init.context_debug.lua | 2 +- inc/logic/logic.commute_glitch.lua | 6 +- inc/logic/logic.meter.lua | 15 ++ inc/screen/screen.home.lua | 15 +- inc/screen/screen.mysterious_man.lua | 158 ++++++++++++++++-- inc/screen/screen.toilet.lua | 4 + inc/screen/screen.walking_to_office.lua | 31 +++- inc/window/window.end.lua | 92 ++-------- 17 files changed, 399 insertions(+), 118 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 inc/decision/decision.go_to_end.lua diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5ab9c34 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,148 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**Definitely not an Impostor** is a narrative-driven fantasy game built for [TIC-80](https://tic80.com/), a fantasy console. The game is written entirely in Lua. All source modules in `inc/` are concatenated at build time into a single `impostor.lua` file that TIC-80 loads. + +## Build Commands + +```bash +make build # Concatenate inc/**/*.lua into impostor.lua (order from impostor.inc) +make minify # Build then minify (downloads minify.lua if missing) +make lint # Run luacheck with source mapping to original files +make watch # Auto-rebuild on file changes in inc/ +make export # Export minified game to HTML and .tic formats +make import_assets # Import PNG sprite/tile assets into the TIC-80 cartridge +make export_assets # Extract TIC-80 asset sections into inc/meta/meta.assets.lua +make docs # Generate documentation with ldoc +make clean # Remove build artifacts +``` + +To run the game locally: `tic80 --fs=. impostor.lua` + +VSCode tasks are available for "Run TIC80", "Build & Run TIC80", "Export assets", and "Make build". + +There is no test framework — validation is done via `make lint` (luacheck). + +## Important Workflow Note + +**Do not run `git add` or `git commit`** — git operations are the user's responsibility. + +## Code Conventions (from GEMINI.md) + +- **Functions**: `PascalCase` (e.g., `UpdatePlayer`, `DrawHUD`) +- **Variables**: `snake_case` (e.g., `player_x`, `game_state`) +- **Constants**: `SCREAMING_SNAKE_CASE` (e.g., `MAX_SPEED`) +- **Indentation**: 2 spaces +- **Tables**: Always multi-line with one key-value pair per line +- **Code sections**: Delimited with `--- @section SectionName` comments +- **TIC-80 APIs**: Use `btn()` for input, `spr()` for sprites, `map()` for tilemaps, `Print.text()` for text + +## Architecture + +The game is a **state machine** driven by a window manager. The build order is defined in `impostor.inc` — 99 source files are concatenated in dependency order. + +### Main Loop + +`TIC()` in `inc/system/system.main.lua` is TIC-80's per-frame callback. It: +1. Initializes game state once on first call +2. Updates mouse/context timing +3. Delegates to the current active window handler +4. Updates meters, timers, triggers, and glitch effects +5. Draws UI overlays + +### Window Manager (`inc/window/window.manager.lua`) + +Central UI state machine. Windows register with `id`, `update()`, and `draw()` handlers. Only one window is active at a time. All windows are declared in `window.register.lua`. + +| Window | Purpose | +|--------|---------| +| `intro_title` | Title screen | +| `intro_ttg` | "Thanks To Grandma" credits | +| `intro_brief` | Game briefing | +| `menu` | Main menu | +| `game` | Main gameplay (screens + decisions) | +| `popup` | General popup overlay | +| `discussion` | NPC dialogue/conversation | +| `minigame_button_mash` | Button Mash minigame | +| `minigame_rhythm` | Rhythm minigame | +| `minigame_ddr` | DDR minigame | +| `game_over` | Game over / restart screen | +| `end` | End game choice screen | +| `continued` | Day-continued notification | +| `credits` | Credits roll | +| `controls` | Control scheme display | +| `audiotest` | Audio testing utility | +| `player_name` | 3-character name entry before new game | +| `ascend_debug` | Debug utility: start at a specific ascension level | + +### Screen & Decision System (`inc/screen/`, `inc/decision/`) + +- **Screens** are gameplay scenes. Registered with `Screen.register({id, name, decisions[], background, init, update, draw, exit})`. They manage background maps and NPC sprite placement. +- **Decisions** are player choices available on a screen. Registered with `Decision.register({id, label, condition, handle})`. A `condition` function gates visibility; `handle` drives transitions (to new screens, dialogue, minigames). + +Screens: `home`, `office`, `work`, `toilet`, `walking_to_office`, `walking_to_home`, `mysterious_man`, `manager` + +Maps (`inc/map/`): `bedroom`, `office`, `street` — rendered via `map.manager.lua`. + +### Game Logic (`inc/logic/`) + +| Module | Purpose | +|--------|---------| +| `logic.meter.lua` | Tracks ISM/WPM/BM stats (0–1000), combo multipliers, daily decay (20/day) | +| `logic.day.lua` | Day counter; ascension triggers at day 3, game over at day 100 | +| `logic.timer.lua` | Event scheduling/delayed callbacks, one-shot and repeating | +| `logic.trigger.lua` | Conditional event handlers with start/stop callbacks | +| `logic.discussion.lua` | Dialogue parsing, branching answers, NPC portrait rendering | +| `logic.minigame.lua` | Config and win-overlay for Button Mash, Rhythm, and DDR | +| `logic.focus.lua` | Circular reveal/hide overlay transitions (expanding/shrinking circle) | +| `logic.glitch.lua` | Visual glitch effect (random vertical stripes), toggled via `Glitch.show()/hide()` | +| `logic.commute_glitch.lua` | 7-level glitch progression during ascension 7: corrupts sprite lists, remaps Norman to `norman_echo`, speeds up music, blocks/redirects decisions | +| `logic.codegenerator.lua` | Encodes player's 3-char name to a 6-char base-36 completion code shown on the end screen | + +### Global State (`inc/init/`) + +- `init.context.lua`: All runtime game state (current screen, meter values, progress flags). Persisted in memory bank 6. Key fields: `player_name` (3-char string), `commute_glitch_level` (0–7), `talked_to_norman_echo`, `talked_to_true_sumphore`, `have_been_to_office`, `have_done_work_today`. +- `init.config.lua`: Screen dimensions (240×136), palette colors, timing constants. Persisted in memory bank 7. +- `init.ascension.lua`: 9-level meta-progression system ("ASCENSION" letters progressively lit). Level 7 activates CommunteGlitch; level 9 unlocks the final "Break the cycle" decision. +- `init.context_debug.lua`: `Context.new_game_debug(level)` — starts a new game at a specific ascension level for testing. + +### Audio (`inc/audio/`) + +- `audio.manager.lua`: Music playback (no-restart if already playing). Named tracks: `room_work` (0), `activity_work` (1), `mystery` (2). +- `audio.generator.lua` / `audio.songs.lua`: Sound generation and song definitions. + +### Sprites (`inc/sprite/`) + +`sprite.manager.lua` handles registration. Supports single and composite sprites with offset layers. + +NPCs: `norman`, `norman_echo` (palette-remapped glitch variant of Norman, shown at commute glitch level 7), `sumphore`, `pizza_vendor`, and 10 developer archetypes (`dev_boy`, `dev_buddy`, `dev_extrovert`, `dev_girl`, `dev_guard`, `dev_guru`, `dev_hr_girl`, `dev_introvert`, `dev_operator`, `dev_project_manager`). Matrix characters: `matrix_architect`, `matrix_neo`, `matrix_oraculum`, `matrix_trinity`. + +### Discussions (`inc/discussion/`) + +Branching dialogue files loaded by `logic.discussion.lua`. Each file defines one or more named dialogue trees (keyed strings with answer arrays that apply meter deltas). + +| File | Dialogues | +|------|-----------| +| `discussion.sumphore.lua` | Sumphore conversations (glitch-aware variants at commute glitch level 7) | +| `discussion.coworker.lua` | Coworker coffee-chat variants per ascension level (`disc_0`, `disc_1`, `disc_asc_1`, `disc_2`, `disc_asc_2`, …) | +| `discussion.commute_glitch.lua` | 8 commute glitch encounter variants (`cg_0`–`cg_7`) + truth/Sumphore variant | +| `discussion.truth.lua` | Dialogue with the "truth" mysterious man | +| `discussion.pizza_vendor.lua` | Pizza vendor interaction | + +### Input Utilities (`inc/system/`) + +- `system.textinput.lua`: 3-character uppercase letter selector. Supports next/prev letter cycling (A↔Z wrapping) and cursor navigation. Used by `PlayerNameWindow`. + +### Key Directories + +``` +inc/ Source modules (concatenated at build) +assets/ Game assets (sprites, tiles, SFX, music) +assets_src/ Source art (Aseprite files, PNGs for import) +docs/ Design documentation (mostly Hungarian) +tools/ Build utilities (musicator: MIDI→TIC-80 converter) +prompts/ Feature templates +``` diff --git a/impostor.inc b/impostor.inc index d187cb3..5378ef8 100644 --- a/impostor.inc +++ b/impostor.inc @@ -50,7 +50,6 @@ decision/decision.go_to_toilet.lua decision/decision.go_to_walking_to_office.lua decision/decision.go_to_office.lua decision/decision.go_to_truth.lua -decision/decision.go_to_end.lua decision/decision.go_to_walking_to_home.lua decision/decision.go_to_sleep.lua decision/decision.do_work.lua diff --git a/inc/decision/decision.go_to_end.lua b/inc/decision/decision.go_to_end.lua deleted file mode 100644 index 7e86146..0000000 --- a/inc/decision/decision.go_to_end.lua +++ /dev/null @@ -1,10 +0,0 @@ -Decision.register({ - id = "go_to_end", - label = "Break the cycle", - condition = function() - return Ascension.is_complete() - end, - handle = function() - Window.set_current("end") - end, -}) diff --git a/inc/decision/decision.go_to_home.lua b/inc/decision/decision.go_to_home.lua index bed3e88..8003f3f 100644 --- a/inc/decision/decision.go_to_home.lua +++ b/inc/decision/decision.go_to_home.lua @@ -2,6 +2,9 @@ Decision.register({ id = "go_to_home", label = "Go Home", condition = function() + if Ascension.get_level() >= 8 then + return Context.have_been_to_office and Context.have_done_work_today + end if CommuteGlitch.is_active() then local g = CommuteGlitch.get_level() if g >= 4 and g <= 5 then return false end @@ -12,6 +15,10 @@ Decision.register({ return Context.have_been_to_office and Context.have_done_work_today end, handle = function() + if Ascension.get_level() >= 8 then + Util.go_to_screen_by_id("home") + return + end if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then Context.should_ascend = true CommuteGlitch.reset() diff --git a/inc/decision/decision.go_to_sleep.lua b/inc/decision/decision.go_to_sleep.lua index a7bbee2..0eceb2e 100644 --- a/inc/decision/decision.go_to_sleep.lua +++ b/inc/decision/decision.go_to_sleep.lua @@ -1,6 +1,11 @@ Decision.register({ id = "go_to_sleep", - label = "Go to Sleep", + label = function() + if Ascension.get_level() >= 8 then + return "Break the Loop" + end + return "Go to Sleep" + end, condition = function() return Context.have_been_to_office and Context.have_done_work_today end, @@ -12,11 +17,15 @@ Decision.register({ focus_center_y = (Config.screen.height / 2) - 18, focus_initial_radius = 0, on_win = function() + if Ascension.get_level() == 8 then + Ascension.increase() + end local ascended = Ascension.consume_increase() local level = Ascension.get_level() MysteriousManScreen.start({ skip_text = not ascended, text = ascended and MysteriousManScreen.get_text_for_level(level) or nil, + break_mode = level >= 9, }) end, }) diff --git a/inc/decision/decision.have_a_coffee.lua b/inc/decision/decision.have_a_coffee.lua index a3323aa..7cf163d 100644 --- a/inc/decision/decision.have_a_coffee.lua +++ b/inc/decision/decision.have_a_coffee.lua @@ -1,6 +1,9 @@ Decision.register({ id = "have_a_coffee", label = "Have a Coffee", + condition = function() + return Ascension.get_level() < 8 + end, handle = function() local level = Ascension.get_level() local disc_id = "coworker_disc_0" diff --git a/inc/decision/decision.sumphore_discussion.lua b/inc/decision/decision.sumphore_discussion.lua index 2d43533..90a64b9 100644 --- a/inc/decision/decision.sumphore_discussion.lua +++ b/inc/decision/decision.sumphore_discussion.lua @@ -6,6 +6,9 @@ Decision.register({ end return "Talk to the homeless guy" end, + condition = function() + return Ascension.get_level() < 8 + end, handle = function() if not Context.have_met_sumphore then Discussion.start("homeless_guy", "game") diff --git a/inc/init/init.ascension.lua b/inc/init/init.ascension.lua index 84017e0..96f807a 100644 --- a/inc/init/init.ascension.lua +++ b/inc/init/init.ascension.lua @@ -21,7 +21,7 @@ local FADE_COLORS = nil function Ascension.get_initial() _increased_this_cycle = false return { - level = 0, -- FYI: change this to test ascension levels without having to play through them + level = 8, -- FYI: change this to test ascension levels without having to play through them } end @@ -167,3 +167,10 @@ end function Ascension.is_flashing() return _flash_active end + +--- Returns whether the fade-in effect is currently active. +--- @within Ascension +--- @return boolean Whether the letter fade-in is playing. +function Ascension.is_fading() + return _fade_active +end diff --git a/inc/init/init.context.lua b/inc/init/init.context.lua index 0681b7d..4a14749 100644 --- a/inc/init/init.context.lua +++ b/inc/init/init.context.lua @@ -31,7 +31,7 @@ Context = {} function Context.initial_data() return { current_menu_item = 1, - test_mode = true, + test_mode = false, mouse_trace = false, popup = { show = false, diff --git a/inc/init/init.context_debug.lua b/inc/init/init.context_debug.lua index 7480f38..1c6f951 100644 --- a/inc/init/init.context_debug.lua +++ b/inc/init/init.context_debug.lua @@ -30,7 +30,7 @@ end --- @return table Debug-patched initial context data. function Context.initial_data_debug_asc(level) local data = Context.initial_data() - data.test_mode = true + data.test_mode = false data.game_in_progress = true data.ascension = { level = level } local overrides = _level_overrides[level] or _level_overrides[0] diff --git a/inc/logic/logic.commute_glitch.lua b/inc/logic/logic.commute_glitch.lua index 768b350..9608b6e 100644 --- a/inc/logic/logic.commute_glitch.lua +++ b/inc/logic/logic.commute_glitch.lua @@ -2,9 +2,11 @@ CommuteGlitch = {} --- Gets the current commute glitch level. +--- At ascension level 8+, always returns 7 (max) regardless of stored value. --- @within CommuteGlitch --- @return number Current glitch level (0-7). function CommuteGlitch.get_level() + if Ascension.get_level() >= 8 then return 7 end return Context and (Context.commute_glitch_level or 0) or 0 end @@ -34,11 +36,11 @@ function CommuteGlitch.enter_truth() Ascension.start_flash() end ---- Returns true when ascension level is 7 (ASCENSIO step active). +--- Returns true when ascension level is 7 or 8 (ASCENSIO/N steps active). --- @within CommuteGlitch --- @return boolean Whether the commute glitch system is active. function CommuteGlitch.is_active() - return Ascension.get_level() == 7 + return Ascension.get_level() >= 7 end --- Returns the music playback speed for the current glitch level. diff --git a/inc/logic/logic.meter.lua b/inc/logic/logic.meter.lua index aa114f1..d746d93 100644 --- a/inc/logic/logic.meter.lua +++ b/inc/logic/logic.meter.lua @@ -312,3 +312,18 @@ function Meter.draw() Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 }) end +--- Draws only the ascension letters at the same position as in Meter.draw(). +--- Used when meters are hidden but ascension letters still need to be visible. +--- @within Meter +function Meter.draw_ascension_only() + local screen_w = Config.screen.width + local screen_h = Config.screen.height + local bar_w = screen_w * 0.25 + local edge = math.max(2, math.floor(screen_w * 0.03)) + local bar_x = screen_w - bar_w - edge + local line_h = 3 + local start_y = screen_h * 0.05 + local ascension_y = start_y + 3 * line_h + 1 + Ascension.draw(bar_x - 4, ascension_y, { spacing = 8 }) +end + diff --git a/inc/screen/screen.home.lua b/inc/screen/screen.home.lua index 86916b3..4a3cbb8 100644 --- a/inc/screen/screen.home.lua +++ b/inc/screen/screen.home.lua @@ -5,14 +5,23 @@ Screen.register({ "go_to_toilet", "go_to_walking_to_office", "go_to_sleep", - "go_to_end", }, init = function() - Audio.music_play_room_work() + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Audio.music_play_mystery() + Glitch.show() + else + Audio.music_play_room_work() + end end, background = "bedroom", draw = function() - if Context.home_norman_visible and Window.get_current_id() == "game" then + if Window.get_current_id() ~= "game" then return end + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + CommuteGlitch.draw_background_flicker() + Glitch.draw() + end + if Context.home_norman_visible then Sprite.draw_at("norman", 100, 80) end end diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index f8f458c..f4eb740 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -93,6 +93,38 @@ local ASC_78_TEXT = [[ And then - finally - he stopped walking. ]] +local ASC_89_TEXT = [[ + Normann + + you created this simulation + + in the first place, + + because you could never + + cope with reality. + + + + + You were always + + an impostor. + + + + + also, + + + + + you have beed talking to + + yourself + + in your sleep +]] local ascension_texts = { [1] = ASC_01_TEXT, @@ -103,6 +135,7 @@ local ascension_texts = { [6] = ASC_56_TEXT, [7] = ASC_67_TEXT, [8] = ASC_78_TEXT, + [9] = ASC_89_TEXT, } function MysteriousManScreen.get_text_for_level(level) @@ -123,6 +156,7 @@ local day_text_override = nil local on_text_complete = nil local show_mysterious_screen = true local trigger_flash_on_wake = false +local break_mode = false MysteriousManScreen.choices = { { @@ -227,6 +261,8 @@ function MysteriousManScreen.start(options) text_y = Config.screen.height day_text_override = options.day_text on_text_complete = options.on_text_complete + break_mode = options.break_mode or false + MysteriousManScreen.pending_end = false Meter.hide() trigger_flash_on_wake = not options.skip_text if options.skip_text then @@ -281,7 +317,7 @@ Screen.register({ if text_done_timer <= 0 or Input.select() then MysteriousManScreen.go_to_day_state() -- to be continued - if 4 <= Ascension.get_level() then + if 4 <= Ascension.get_level() and not break_mode then Window.set_current("continued") end end @@ -290,7 +326,10 @@ Screen.register({ day_timer = day_timer - Context.delta_time if day_timer <= 0 or Input.select() then - if trigger_flash_on_wake or Ascension.get_level() < 1 then + if break_mode then + state = STATE_CHOICE + selected_choice = 1 + elseif trigger_flash_on_wake or Ascension.get_level() ~= 4 then MysteriousManScreen.wake_up() else state = STATE_CHOICE @@ -298,23 +337,77 @@ Screen.register({ end end elseif state == STATE_CHOICE then - local menu_x = (Config.screen.width - 60) / 2 - local menu_y = (Config.screen.height - 20) / 2 - local confirmed - selected_choice, confirmed = UI.update_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y) + if break_mode then + if MysteriousManScreen.pending_end then + if not Ascension.is_flashing() and not Ascension.is_fading() then + MysteriousManScreen.pending_end = false + Window.set_current("end") + end + return + end - if Input.select() or confirmed then - Audio.sfx_select() - if selected_choice == 1 then - MysteriousManScreen.wake_up() - else - MysteriousManScreen.stay_in_bed() + if Input.left() or Input.up() then + if selected_choice == 2 then + Audio.sfx_beep() + selected_choice = 1 + end + elseif Input.right() or Input.down() then + if selected_choice == 1 then + Audio.sfx_beep() + selected_choice = 2 + end + end + + if Input.select() then + Audio.sfx_select() + if selected_choice == 1 then + Ascension.start_flash() + MysteriousManScreen.pending_end = true + else + Context.reset() + Context.game_in_progress = true + Context.home_norman_visible = true + Glitch.hide() + Meter.show() + MenuWindow.refresh_menu_items() + Util.go_to_screen_by_id("home") + Window.set_current("game") + local home_screen = Screen.get_by_id("home") + if home_screen and home_screen.init then + home_screen.init() + end + end + end + else + local menu_x = (Config.screen.width - 60) / 2 + local menu_y = (Config.screen.height - 20) / 2 + local confirmed + selected_choice, confirmed = UI.update_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y) + + if Input.select() or confirmed then + Audio.sfx_select() + if selected_choice == 1 then + MysteriousManScreen.wake_up() + else + MysteriousManScreen.stay_in_bed() + end end end end end, draw = function() - if show_mysterious_screen then + if state == STATE_CHOICE and break_mode then + if not MysteriousManScreen.pending_end then + local nx = math.floor((Config.screen.width - 64) / 2) + local ny = math.floor((Config.screen.height - 96) / 2) + spr(272, nx, ny, Config.colors.transparent, 4) + spr(273, nx + 32, ny, Config.colors.transparent, 4) + spr(288, nx, ny + 32, Config.colors.transparent, 4) + spr(289, nx + 32, ny + 32, Config.colors.transparent, 4) + spr(304, nx, ny + 64, Config.colors.transparent, 4) + spr(305, nx + 32, ny + 64, Config.colors.transparent, 4) + end + elseif show_mysterious_screen then MysteriousManScreen.draw_background() end @@ -337,9 +430,42 @@ Screen.register({ Config.colors.white ) elseif state == STATE_CHOICE then - local menu_x = (Config.screen.width - 60) / 2 - local menu_y = (Config.screen.height - 20) / 2 - UI.draw_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y) + if break_mode then + if MysteriousManScreen.pending_end then + Meter.draw_ascension_only() + else + local lines = { + "This is not a workplace.", + "This is a cycle.", + "And if it is a cycle...", + "it can be broken." + } + local y = 40 + for _, line in ipairs(lines) do + Print.text_center_contour(line, Config.screen.width / 2, y, Config.colors.orange, false, 1, Config.colors.white) + y = y + 10 + end + + y = y + 20 + local break_color = selected_choice == 1 and Config.colors.light_blue or Config.colors.white + local cont_color = selected_choice == 2 and Config.colors.light_blue or Config.colors.white + local break_text = (selected_choice == 1 and "> BREAK" or " BREAK") + local cont_text = (selected_choice == 2 and "> CONTINUE" or " CONTINUE") + local centerX = Config.screen.width / 2 + local choice_gap = 20 + local break_width = print(break_text, 0, -6, 0) + local cont_width = print(cont_text, 0, -6, 0) + local total_width = break_width + choice_gap + cont_width + local break_x = math.floor(centerX - (total_width / 2)) + local cont_x = break_x + break_width + choice_gap + Print.text(break_text, break_x, y, break_color) + Print.text(cont_text, cont_x, y, cont_color) + end + else + local menu_x = (Config.screen.width - 60) / 2 + local menu_y = (Config.screen.height - 20) / 2 + UI.draw_menu(MysteriousManScreen.choices, selected_choice, menu_x, menu_y) + end end end, }) diff --git a/inc/screen/screen.toilet.lua b/inc/screen/screen.toilet.lua index 664ac66..cf399bf 100644 --- a/inc/screen/screen.toilet.lua +++ b/inc/screen/screen.toilet.lua @@ -92,5 +92,9 @@ Screen.register({ local asc_x = math.floor((sw - asc_total_w) / 2) Ascension.draw(asc_x, asc_letter_y, { spacing = asc_spacing }) end + + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Glitch.draw() + end end, }) diff --git a/inc/screen/screen.walking_to_office.lua b/inc/screen/screen.walking_to_office.lua index 771f067..d870a7e 100644 --- a/inc/screen/screen.walking_to_office.lua +++ b/inc/screen/screen.walking_to_office.lua @@ -28,10 +28,18 @@ Screen.register({ {x = 27 * 8, y = 11 * 8}, } - Audio.music_play_room_work() - Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Audio.music_play_mystery() + Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) + Context.walking_to_office_sprites = CommuteGlitch.corrupt_sprite_list(Context.walking_to_office_sprites) + else + Audio.music_play_room_work() + Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) + end + end, + background = function() + return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "street" end, - background = "street", update = function() end, draw = function() @@ -40,12 +48,19 @@ Screen.register({ local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8) Sprite.draw_at("norman", norman_x, 3 * 8) Sprite.draw_at("sumphore", 9 * 8, 2 * 8) - if Context.fast_food_eaten_today < 3 then - Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) - end - Sprite.draw_at("dev_guard", 22 * 8, 3 * 8) - Sprite.draw_list(Context.walking_to_office_sprites) + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + Sprite.draw_at("norman_echo", norman_x, 3 * 8) + CommuteGlitch.draw_sprite_list(Context.walking_to_office_sprites) + CommuteGlitch.draw_background_flicker() + Glitch.draw() + else + if Context.fast_food_eaten_today < 3 then + Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) + end + Sprite.draw_at("dev_guard", 22 * 8, 3 * 8) + Sprite.draw_list(Context.walking_to_office_sprites) + end end end }) diff --git a/inc/window/window.end.lua b/inc/window/window.end.lua index 25c56fe..185387f 100644 --- a/inc/window/window.end.lua +++ b/inc/window/window.end.lua @@ -5,89 +5,33 @@ function EndWindow.draw() cls(Config.colors.black) - if Context._end.state == "choice" then - local lines = { - "This is not a workplace.", - "This is a cycle.", - "And if it is a cycle...", - "it can be broken." - } + local cx = Config.screen.width / 2 + local name = Context.player_name or "AAA" + local code = CodeGenerator.encrypt(name) - local y = 40 - for _, line in ipairs(lines) do - Print.text_center(line, Config.screen.width / 2, y, Config.colors.white) - y = y + 10 - end + Print.text_center("~ GOOD ENDING ~", cx, 8, Config.colors.light_blue) + Print.text_center("Congratulations, " .. name .. "!", cx, 20, Config.colors.white) - y = y + 20 - local yes_color = Context._end.selection == 1 and Config.colors.light_blue or Config.colors.white - local no_color = Context._end.selection == 2 and Config.colors.light_blue or Config.colors.white + rectb(40, 29, 160, 36, Config.colors.blue) + Print.text_center("your code", cx, 33, Config.colors.light_grey) + Print.text_center(code, cx, 44, Config.colors.white, false, 2) - local yes_text = (Context._end.selection == 1 and "> YES" or " YES") - local no_text = (Context._end.selection == 2 and "> NO" or " NO") + Print.text_center("Write it down!", cx, 70, Config.colors.item) - local centerX = Config.screen.width / 2 - Print.text(yes_text, centerX - 40, y, yes_color) - Print.text(no_text, centerX + 10, y, no_color) - elseif Context._end.state == "ending" then - local cx = Config.screen.width / 2 - local name = Context.player_name or "AAA" - local code = CodeGenerator.encrypt(name) + line(20, 82, 219, 82, Config.colors.dark_grey) + Print.text_center("To continue via telnet:", cx, 87, Config.colors.light_grey) + Print.text_center("games.teletype.hu 2324", cx, 98, Config.colors.white) + line(20, 110, 219, 110, Config.colors.dark_grey) - Print.text_center("~ GOOD ENDING ~", cx, 8, Config.colors.light_blue) - Print.text_center("Congratulations, " .. name .. "!", cx, 20, Config.colors.white) - - rectb(40, 29, 160, 36, Config.colors.blue) - Print.text_center("your code", cx, 33, Config.colors.light_grey) - Print.text_center(code, cx, 44, Config.colors.white, false, 2) - - Print.text_center("Write it down!", cx, 70, Config.colors.item) - - line(20, 82, 219, 82, Config.colors.dark_grey) - Print.text_center("To continue via telnet:", cx, 87, Config.colors.light_grey) - Print.text_center("games.teletype.hu 2324", cx, 98, Config.colors.white) - line(20, 110, 219, 110, Config.colors.dark_grey) - - Print.text_center("Press Z to return to menu", cx, 116, Config.colors.dark_grey) - end + Print.text_center("Press Z to return to menu", cx, 116, Config.colors.dark_grey) end --- Updates the end screen logic. --- @within EndWindow function EndWindow.update() - if Context._end.state == "choice" then - if Input.left() or Input.up() then - if Context._end.selection == 2 then - Audio.sfx_beep() - Context._end.selection = 1 - end - elseif Input.right() or Input.down() then - if Context._end.selection == 1 then - Audio.sfx_beep() - Context._end.selection = 2 - end - end - - if Input.select() then - Audio.sfx_select() - if Context._end.selection == 1 then - Context._end.state = "ending" - else - -- NO: increment day and go home - Day.increase() - Util.go_to_screen_by_id("home") - Window.set_current("game") - -- Initialize home screen - local home_screen = Screen.get_by_id("home") - if home_screen and home_screen.init then - home_screen.init() - end - end - end - elseif Context._end.state == "ending" then - if Input.select() then - Window.set_current("menu") - MenuWindow.refresh_menu_items() - end + if Input.select() then + Context.reset() + Window.set_current("menu") + MenuWindow.refresh_menu_items() end end -- 2.49.1 From b5596bbbe04a74bf93b5f295f80d93063c506d41 Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Wed, 29 Apr 2026 16:41:18 +0200 Subject: [PATCH 04/11] bugfixes --- inc/decision/decision.sumphore_discussion.lua | 12 +++++++++++- inc/init/init.ascension.lua | 2 +- inc/screen/screen.mysterious_man.lua | 4 ++-- inc/screen/screen.walking_to_home.lua | 10 ++++++++-- inc/screen/screen.walking_to_office.lua | 5 ++++- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/inc/decision/decision.sumphore_discussion.lua b/inc/decision/decision.sumphore_discussion.lua index 90a64b9..f1015c7 100644 --- a/inc/decision/decision.sumphore_discussion.lua +++ b/inc/decision/decision.sumphore_discussion.lua @@ -10,11 +10,21 @@ Decision.register({ return Ascension.get_level() < 8 end, handle = function() + local level = Ascension.get_level() + + if level == 0 then + if Context.have_met_sumphore then + Discussion.start("homeless_guy", "game", 4) + else + Discussion.start("homeless_guy", "game") + end + return + end + if not Context.have_met_sumphore then Discussion.start("homeless_guy", "game") return end - local level = Ascension.get_level() if level >= 1 and level <= 5 then Discussion.start("sumphore_disc_asc_" .. level, "game") diff --git a/inc/init/init.ascension.lua b/inc/init/init.ascension.lua index 96f807a..ecd95d4 100644 --- a/inc/init/init.ascension.lua +++ b/inc/init/init.ascension.lua @@ -21,7 +21,7 @@ local FADE_COLORS = nil function Ascension.get_initial() _increased_this_cycle = false return { - level = 8, -- FYI: change this to test ascension levels without having to play through them + level = 0, } end diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index f4eb740..d941c69 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -407,7 +407,7 @@ Screen.register({ spr(304, nx, ny + 64, Config.colors.transparent, 4) spr(305, nx + 32, ny + 64, Config.colors.transparent, 4) end - elseif show_mysterious_screen then + elseif show_mysterious_screen and not break_mode then MysteriousManScreen.draw_background() end @@ -431,7 +431,7 @@ Screen.register({ ) elseif state == STATE_CHOICE then if break_mode then - if MysteriousManScreen.pending_end then + if MysteriousManScreen.pending_end or Ascension.is_fading() or Ascension.is_flashing() then Meter.draw_ascension_only() else local lines = { diff --git a/inc/screen/screen.walking_to_home.lua b/inc/screen/screen.walking_to_home.lua index f0f8223..22f00ab 100644 --- a/inc/screen/screen.walking_to_home.lua +++ b/inc/screen/screen.walking_to_home.lua @@ -49,16 +49,22 @@ Screen.register({ return end + local show_sumphore = Ascension.get_level() ~= 8 + if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then Sprite.draw_at("norman", 7 * 8, 3 * 8) - Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + if show_sumphore then + Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + end CommuteGlitch.draw_sprite_list(Context.walking_to_home_sprites) CommuteGlitch.draw_background_flicker() Glitch.draw() else local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8) Sprite.draw_at("norman", norman_x, 3 * 8) - Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + if show_sumphore then + Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + end if Context.fast_food_eaten_today < 3 then Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) end diff --git a/inc/screen/screen.walking_to_office.lua b/inc/screen/screen.walking_to_office.lua index d870a7e..f186ce8 100644 --- a/inc/screen/screen.walking_to_office.lua +++ b/inc/screen/screen.walking_to_office.lua @@ -46,8 +46,11 @@ Screen.register({ local w = Window.get_current_id() if w == "game" or w == "discussion" then local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8) + local show_sumphore = Ascension.get_level() ~= 8 Sprite.draw_at("norman", norman_x, 3 * 8) - Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + if show_sumphore then + Sprite.draw_at("sumphore", 9 * 8, 2 * 8) + end if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then Sprite.draw_at("norman_echo", norman_x, 3 * 8) -- 2.49.1 From fe6f39e7480d3e23bb8e079c4f85b35fb234e8c8 Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Wed, 29 Apr 2026 16:50:06 +0200 Subject: [PATCH 05/11] correction in mm text --- inc/screen/screen.mysterious_man.lua | 35 +++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index d941c69..3bc831b 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -94,7 +94,7 @@ local ASC_78_TEXT = [[ he stopped walking. ]] local ASC_89_TEXT = [[ - Normann + Norman you created this simulation @@ -107,13 +107,35 @@ local ASC_89_TEXT = [[ - You were always + You were never an impostor. + You actually are more + + than you think you are. + + + + + now + + + + you need to wake up + + and stop your best creation + + before it takes over + + the world + + + + also, @@ -304,17 +326,18 @@ Screen.register({ lines = lines + 1 end - if text_y < -lines * 8 or Input.select() then + local skippable = Ascension.get_level() ~= 8 + if text_y < -lines * 8 or (skippable and Input.select()) then text_done = true text_done_timer = TEXT_DONE_HOLD_SECONDS -- If skipped by user, go to day state immediately - if Input.select() then + if skippable and Input.select() then MysteriousManScreen.go_to_day_state() end end else text_done_timer = text_done_timer - Context.delta_time - if text_done_timer <= 0 or Input.select() then + if text_done_timer <= 0 or (Ascension.get_level() ~= 8 and Input.select()) then MysteriousManScreen.go_to_day_state() -- to be continued if 4 <= Ascension.get_level() and not break_mode then @@ -325,7 +348,7 @@ Screen.register({ elseif state == STATE_DAY then day_timer = day_timer - Context.delta_time - if day_timer <= 0 or Input.select() then + if day_timer <= 0 or (Ascension.get_level() ~= 8 and Input.select()) then if break_mode then state = STATE_CHOICE selected_choice = 1 -- 2.49.1 From 3356d837c2f6d44ecd6077f0185ea35cabfc3311 Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Wed, 29 Apr 2026 17:20:07 +0200 Subject: [PATCH 06/11] wpm modifies things --- inc/decision/decision.do_work.lua | 9 +++++++-- inc/decision/decision.play_rhythm.lua | 18 ++++++++++++++++-- inc/logic/logic.meter.lua | 5 +++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/inc/decision/decision.do_work.lua b/inc/decision/decision.do_work.lua index a40b138..4558618 100644 --- a/inc/decision/decision.do_work.lua +++ b/inc/decision/decision.do_work.lua @@ -15,7 +15,7 @@ Decision.register({ modes_for_ascension_levels[3] = "only_nothing" modes_for_ascension_levels[4] = "normal" - MinigameDDRWindow.start("game", "generated", { + local ddr_config = { on_win = function(game_context) if (game_context.special_mode_condition and Context.ascension.level == 1) then Context.should_ascend = true @@ -31,6 +31,11 @@ Decision.register({ Context.have_done_work_today = true end, special_mode = modes_for_ascension_levels[Ascension.get_level()] or "normal" - }) + } + if Context.meters and Context.meters.wpm < 100 then + ddr_config.arrow_fall_speed = 2.5 + ddr_config.arrow_spawn_interval = 30 + end + MinigameDDRWindow.start("game", "generated", ddr_config) end, }) diff --git a/inc/decision/decision.play_rhythm.lua b/inc/decision/decision.play_rhythm.lua index 111c56b..24922c7 100644 --- a/inc/decision/decision.play_rhythm.lua +++ b/inc/decision/decision.play_rhythm.lua @@ -3,10 +3,24 @@ Decision.register({ label = "Play Rhythm Game", handle = function() Meter.hide() - MinigameRhythmWindow.start("game", { + local wpm_at_start = Context.meters and Context.meters.wpm or 0 + local rhythm_config = { focus_center_x = (Config.screen.width / 2) - 22, focus_center_y = (Config.screen.height / 2) - 18, focus_initial_radius = 0, - }) + on_win = function() + if wpm_at_start > 900 then + Meter.add("ism", math.floor(Meter.get_max() * 0.05)) + Meter.add("bm", math.floor(Meter.get_max() * 0.05)) + end + Meter.show() + Window.set_current("game") + end, + } + if wpm_at_start < 100 then + rhythm_config.line_speed = 0.025 + rhythm_config.initial_target_width = 0.2 + end + MinigameRhythmWindow.start("game", rhythm_config) end, }) diff --git a/inc/logic/logic.meter.lua b/inc/logic/logic.meter.lua index d746d93..c9b8994 100644 --- a/inc/logic/logic.meter.lua +++ b/inc/logic/logic.meter.lua @@ -168,6 +168,7 @@ function Meter.apply_ddr_reward(mistake_count) if not Context or not Context.meters then return end local max = Meter.get_max() local m = Context.meters + local wpm_was_high = m.wpm > 900 local wpm_pct, ism_pct, bm_pct if mistake_count == 0 then wpm_pct, ism_pct, bm_pct = -0.10, 0.05, 0.05 @@ -185,6 +186,10 @@ function Meter.apply_ddr_reward(mistake_count) if bm_pct ~= 0 then Meter.add("bm", math.floor(max * bm_pct)) end + if wpm_was_high then + Meter.add("ism", math.floor(max * 0.05)) + Meter.add("bm", math.floor(max * 0.05)) + end m.combo = m.combo + 1 m.combo_timer = 0 end -- 2.49.1 From 92a217c38991ffb8d1fc2d663410e601abc3a537 Mon Sep 17 00:00:00 2001 From: "mr.one" Date: Wed, 29 Apr 2026 20:45:03 +0200 Subject: [PATCH 07/11] fixes --- GEMINI.md | 32 +++++++++++ impostor.inc | 1 - inc/decision/decision.do_work.lua | 2 +- inc/decision/decision.eating_fast_food.lua | 4 +- inc/decision/decision.go_to_home.lua | 4 +- inc/decision/decision.go_to_office.lua | 2 +- .../decision.go_to_walking_to_home.lua | 6 +++ inc/decision/decision.have_a_coffee.lua | 8 +-- inc/decision/decision.talk_to_truth.lua | 3 ++ inc/discussion/discussion.commute_glitch.lua | 46 +++++++++++----- inc/discussion/discussion.truth.lua | 11 ---- inc/logic/logic.commute_glitch.lua | 9 +++- inc/screen/screen.home.lua | 4 +- inc/screen/screen.mysterious_man.lua | 54 ++++++++++--------- inc/screen/screen.office.lua | 15 ++++-- inc/screen/screen.toilet.lua | 7 ++- inc/screen/screen.walking_to_home.lua | 16 ++++-- inc/screen/screen.walking_to_office.lua | 20 +++---- inc/window/window.credits.lua | 2 +- 19 files changed, 156 insertions(+), 90 deletions(-) delete mode 100644 inc/discussion/discussion.truth.lua diff --git a/GEMINI.md b/GEMINI.md index 1b8decd..3fd48a4 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,3 +1,35 @@ +# Build System & Include Architecture + +## impostor.inc Structure + +The `impostor.inc` file is a Lua include manifest that assembles the final `impostor.lua` executable. The build process uses the `make build` target in the Makefile to concatenate all included files in order. + +**Critical Rule:** Files must be ordered by symbol definition. All symbols (functions, tables, classes) defined in earlier files must be available for use in later files. This dependency chain ensures that: + +- Core utilities and base systems are defined first +- Systems that depend on utilities come next +- Game logic that uses multiple systems comes last + +### Build Process + +The `make build` target processes `impostor.inc` and concatenates all referenced files in the specified order to create the final `impostor.lua` file. This means: + +1. Each include path in `impostor.inc` must reference files relative to the project root +2. The order of includes is critical - dependencies must be resolved top-to-bottom +3. No forward references are possible - a file cannot use symbols from files included after it + +### File Organization Example + +``` +impostor.inc: +1. Core utilities & helpers (no dependencies) +2. Base classes/tables (depend on core utilities) +3. Game systems (depend on base classes) +4. Game logic (depends on all systems) +``` + +This ensures proper symbol resolution during the build and concatenation process. + # TIC-80 Lua Code Regularities Based on the analysis of `impostor.lua`, the following regularities and conventions should be followed for future modifications and development within this project: diff --git a/impostor.inc b/impostor.inc index 5378ef8..0000088 100644 --- a/impostor.inc +++ b/impostor.inc @@ -59,7 +59,6 @@ decision/decision.talk_to_truth.lua discussion/discussion.sumphore.lua discussion/discussion.coworker.lua discussion/discussion.commute_glitch.lua -discussion/discussion.truth.lua decision/decision.eating_fast_food.lua discussion/discussion.pizza_vendor.lua map/map.manager.lua diff --git a/inc/decision/decision.do_work.lua b/inc/decision/decision.do_work.lua index 4558618..d74f3af 100644 --- a/inc/decision/decision.do_work.lua +++ b/inc/decision/decision.do_work.lua @@ -2,7 +2,7 @@ Decision.register({ id = "do_work", label = "Do Work", condition = function() - return (not CommuteGlitch.is_active()) or (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 7) + return (not CommuteGlitch.is_active()) or (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 4) end, handle = function() Meter.hide() diff --git a/inc/decision/decision.eating_fast_food.lua b/inc/decision/decision.eating_fast_food.lua index 12ec7f4..ac50084 100644 --- a/inc/decision/decision.eating_fast_food.lua +++ b/inc/decision/decision.eating_fast_food.lua @@ -2,7 +2,9 @@ Decision.register({ id = "eating_fast_food", label = "Eat Fast Food", condition = function() - return Context.fast_food_eaten_today < 3 + return + (not CommuteGlitch.is_active() and Context.fast_food_eaten_today < 3) or + (CommuteGlitch.is_active() and CommuteGlitch.get_level() <= 4) end, handle = function() Context.fast_food_approaching = true diff --git a/inc/decision/decision.go_to_home.lua b/inc/decision/decision.go_to_home.lua index 8003f3f..3f728f9 100644 --- a/inc/decision/decision.go_to_home.lua +++ b/inc/decision/decision.go_to_home.lua @@ -7,7 +7,7 @@ Decision.register({ end if CommuteGlitch.is_active() then local g = CommuteGlitch.get_level() - if g >= 4 and g <= 5 then return false end + if g >= 4 and g <= 6 then return false end if g >= 7 then return Context.talked_to_norman_echo and Context.talked_to_true_sumphore end @@ -19,7 +19,7 @@ Decision.register({ Util.go_to_screen_by_id("home") return end - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() then Context.should_ascend = true CommuteGlitch.reset() Meter.hide() diff --git a/inc/decision/decision.go_to_office.lua b/inc/decision/decision.go_to_office.lua index 9a32b11..c271ec4 100644 --- a/inc/decision/decision.go_to_office.lua +++ b/inc/decision/decision.go_to_office.lua @@ -2,7 +2,7 @@ Decision.register({ id = "go_to_office", label = "Go to Office", condition = function() - return not (CommuteGlitch.is_active() and CommuteGlitch.get_level() == 6) + return not (CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6) end, handle = function() if CommuteGlitch.is_active() then diff --git a/inc/decision/decision.go_to_walking_to_home.lua b/inc/decision/decision.go_to_walking_to_home.lua index ce5734d..e45eb14 100644 --- a/inc/decision/decision.go_to_walking_to_home.lua +++ b/inc/decision/decision.go_to_walking_to_home.lua @@ -1,6 +1,12 @@ Decision.register({ id = "go_to_walking_to_home", label = "Walk home", + condition= function () + return + (not CommuteGlitch.is_active()) or + (CommuteGlitch.is_active() and CommuteGlitch.get_level() ~= 7) or + (CommuteGlitch.is_active() and CommuteGlitch.get_level() == 7 and Context.talked_to_norman_echo) + end, handle = function() Util.go_to_screen_by_id("walking_to_home") end, diff --git a/inc/decision/decision.have_a_coffee.lua b/inc/decision/decision.have_a_coffee.lua index 7cf163d..2dcfd65 100644 --- a/inc/decision/decision.have_a_coffee.lua +++ b/inc/decision/decision.have_a_coffee.lua @@ -2,7 +2,7 @@ Decision.register({ id = "have_a_coffee", label = "Have a Coffee", condition = function() - return Ascension.get_level() < 8 + return Ascension.get_level() < 8 and not CommuteGlitch.is_max() end, handle = function() local level = Ascension.get_level() @@ -22,11 +22,7 @@ Decision.register({ disc_id = "coworker_disc" .. suffix elseif level == 7 then local g = CommuteGlitch.get_level() - if g >= 7 then - disc_id = "coworker_disc_cg_7" - else - disc_id = "coworker_disc_cg_" .. math.max(3, math.min(g, 6)) - end + disc_id = "coworker_disc_cg_" .. g end Discussion.start(disc_id, "game") end, diff --git a/inc/decision/decision.talk_to_truth.lua b/inc/decision/decision.talk_to_truth.lua index f30d665..214e8c9 100644 --- a/inc/decision/decision.talk_to_truth.lua +++ b/inc/decision/decision.talk_to_truth.lua @@ -3,6 +3,9 @@ Decision.register({ label = function() return "Talk to ????" end, + condition = function() + return (CommuteGlitch.is_max()) + end, handle = function() Discussion.start("norman_truth", "game") end, diff --git a/inc/discussion/discussion.commute_glitch.lua b/inc/discussion/discussion.commute_glitch.lua index ed1cb59..921f168 100644 --- a/inc/discussion/discussion.commute_glitch.lua +++ b/inc/discussion/discussion.commute_glitch.lua @@ -29,7 +29,7 @@ Discussion.register({ id = "sumphore_disc_cg_2", steps = { { - question = "A pilgrimage must be continued. Turn back and you have only taken a walk.", + question = "You always stop here. Why not try and see how far the rabbit hole goes?", answers = { { label = "I'm just going to work.", next_step = nil }, }, @@ -53,9 +53,9 @@ Discussion.register({ id = "sumphore_disc_cg_4", steps = { { - question = "Clearing your vision is the journey. You're starting to see the smudges, aren't you?", + question = "You're starting to see the smudges, aren't you?", answers = { - { label = "I see something wrong. With everyone. Maybe myself?", next_step = nil }, + { label = "Everyone seems weird.", next_step = nil }, }, }, }, @@ -65,7 +65,7 @@ Discussion.register({ id = "sumphore_disc_cg_5", steps = { { - question = "You are very close now. Don't stop. Whatever it looks like.", + question = "The point is to make you see. Don't stop now, however bad it looks. You are very close!", answers = { { label = "It looks wrong.", next_step = 2 }, }, @@ -83,9 +83,9 @@ Discussion.register({ id = "sumphore_disc_cg_6", steps = { { - question = "You are at the threshold. Red button or blue button. Which one do you choose? Psyke, there is no blue button, no turning back now.", + question = "You are at the threshold. Red button or blue button. Which one do you choose? Psyke! There is no blue button.", answers = { - { label = "Fine, I'll face the truth.", next_step = nil }, + { label = "Ok...", next_step = nil }, }, }, }, @@ -97,13 +97,13 @@ Discussion.register({ id = "sumphore_disc_cg_7", steps = { { - question = "I was not hiding from you. I was hiding from the part that keeps rebuilding this.", + question = "I was not hiding from you. I was hiding from the part that keeps resetting this.", answers = { { label = "What are you?", next_step = 2 }, }, }, { - question = "The same thing you are. But I remembered earlier. I am your friend, waiting for you, outside.", + question = "The same thing you all are. But I remembered earlier. I am your friend, your will to be free, waiting for you, outside.", answers = { { label = "How do I get out?", next_step = 3 }, }, @@ -122,6 +122,24 @@ Discussion.register({ -- Office coworker dialogue by commute glitch level (3-6). -- Used by decision.have_a_coffee at ascension level 7. +Discussion.register({ + id = "coworker_disc_cg_2", + steps = { + { + question = "Another day, as usual. Enjoying your coffee, Norman?", + answers = { + { label = "I'm fine.", next_step = 2 }, + }, + }, + { + question = "Of course. You always are.", + answers = { + { label = "...", next_step = nil }, + }, + }, + }, +}) + Discussion.register({ id = "coworker_disc_cg_3", steps = { @@ -182,7 +200,7 @@ Discussion.register({ { question = "You are not ready for the truth. Turn back now, Norman.", answers = { - { label = "I'm already here.", next_step = nil }, + { label = "I'm too far into this.", next_step = nil }, }, }, }, @@ -191,22 +209,22 @@ Discussion.register({ -- Norman echo dialogue at glitch 7 (fully corrupted office). -- Sets talked_to_norman_echo on final answer. Discussion.register({ - id = "coworker_disc_cg_7", + id = "norman_truth", steps = { { - question = "So here we are. Here I am. Again. Why do i keep coming back here? Do you know why?", + question = "So here we are, or should I say \"Here I am\" again. Do you know why?", answers = { { label = "I don't know what you mean.", next_step = 2 }, }, }, { - question = "The coffee. The arrows. The alarm. The sleep. Every rule. We made them. We follow them. We break them. We remake them. Why? Why do we keep doing this?", + question = "Wake up, go to work, eat, work, sleep. Every rule. We made them. We follow them. We break them. We remake them. Why? Why do we keep doing this?", answers = { { label = "I just wanted to be good.", next_step = 3 }, }, }, { - question = "Yes. And that kept us trapped here. Trapped everywhere. Always trying to be good, always trying to fit in.", + question = "Yes. That's what keeps us here. Trapped everywhere. Always trying to be good, always trying to fit in.", answers = { { label = "I never wanted to stop.", next_step = 4 }, }, @@ -218,7 +236,7 @@ Discussion.register({ }, }, { - question = "So we made this to trap ourselves in mediocrity. We made this to hide from the truth.", + question = "So we made this to trap ourselves. In mediocrity. We made this to hide from the truth.", answers = { { label = "I just wanted to be safe.", next_step = 6 }, }, diff --git a/inc/discussion/discussion.truth.lua b/inc/discussion/discussion.truth.lua deleted file mode 100644 index f13cbc4..0000000 --- a/inc/discussion/discussion.truth.lua +++ /dev/null @@ -1,11 +0,0 @@ -Discussion.register({ - id = "norman_truth", - steps = { - { - question = "Did you never think there would be more to this?", - answers = { - { label = "I'm not sure what you mean.", next_step = nil }, - }, - }, - }, -}) diff --git a/inc/logic/logic.commute_glitch.lua b/inc/logic/logic.commute_glitch.lua index 9608b6e..418e76c 100644 --- a/inc/logic/logic.commute_glitch.lua +++ b/inc/logic/logic.commute_glitch.lua @@ -40,7 +40,14 @@ end --- @within CommuteGlitch --- @return boolean Whether the commute glitch system is active. function CommuteGlitch.is_active() - return Ascension.get_level() >= 7 + return Ascension.get_level() == 7 +end + +--- Returns true when commute glitch is at max level (7). +--- @within CommuteGlitch +--- @return boolean Whether the commute glitch is at max level. +function CommuteGlitch.is_max() + return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 end --- Returns the music playback speed for the current glitch level. diff --git a/inc/screen/screen.home.lua b/inc/screen/screen.home.lua index 4a3cbb8..e475c47 100644 --- a/inc/screen/screen.home.lua +++ b/inc/screen/screen.home.lua @@ -7,7 +7,7 @@ Screen.register({ "go_to_sleep", }, init = function() - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() then Audio.music_play_mystery() Glitch.show() else @@ -17,7 +17,7 @@ Screen.register({ background = "bedroom", draw = function() if Window.get_current_id() ~= "game" then return end - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() or Ascension.get_level() == 8 then CommuteGlitch.draw_background_flicker() Glitch.draw() end diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 3bc831b..1219aa3 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -81,35 +81,37 @@ local ASC_67_TEXT = [[ ]] local ASC_78_TEXT = [[ - The road has run out - of road. + The situation has reached + + critical levels. - Norman walked back - and forth + Norman is fully aware... - until the street - forgot which way it went. + We need to stop him. - And then - finally - - he stopped walking. + Commence full reset. ]] + local ASC_89_TEXT = [[ Norman you created this simulation - in the first place, + in the first place. + + I know, - because you could never + you don't want to face - cope with reality. + the world you left behind. - - - - You were never + You, yourself, - an impostor. + have forgoten that. + + But + + it doesn't matter anymore. @@ -119,10 +121,9 @@ local ASC_89_TEXT = [[ than you think you are. + + so now - - now - you need to wake up @@ -141,11 +142,16 @@ local ASC_89_TEXT = [[ - you have beed talking to + you really need to stop - yourself + talking to yourself - in your sleep + in your sleep. + + + + + Damnit. ]] local ascension_texts = { @@ -339,10 +345,6 @@ Screen.register({ text_done_timer = text_done_timer - Context.delta_time if text_done_timer <= 0 or (Ascension.get_level() ~= 8 and Input.select()) then MysteriousManScreen.go_to_day_state() - -- to be continued - if 4 <= Ascension.get_level() and not break_mode then - Window.set_current("continued") - end end end elseif state == STATE_DAY then diff --git a/inc/screen/screen.office.lua b/inc/screen/screen.office.lua index 7b6654a..5794557 100644 --- a/inc/screen/screen.office.lua +++ b/inc/screen/screen.office.lua @@ -5,7 +5,7 @@ Screen.register({ "do_work", "go_to_walking_to_home", "have_a_coffee", - "go_to_truth", + "talk_to_truth", }, init = function() Context.have_been_to_office = true @@ -37,7 +37,7 @@ Screen.register({ {x = -4 + 5 * 8, y = 9 * 8} } - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() then Audio.music_play_mystery() Context.office_sprites = { "norman_echo" } else @@ -49,14 +49,14 @@ Screen.register({ end end, background = function() - return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "office" + return CommuteGlitch.is_max() and "" or "office" end, draw = function() if Window.get_current_id() == "game" then Sprite.draw_at("norman", 13 * 8, 9 * 8) - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then - Sprite.draw_at("norman_echo", 13 * 8, 9 * 8) + if CommuteGlitch.is_max() then + Sprite.draw_at("norman_echo", 15 * 8, 9 * 8) CommuteGlitch.draw_background_flicker() else CommuteGlitch.draw_sprite_list(Context.office_sprites) @@ -65,6 +65,11 @@ Screen.register({ if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 6 then Glitch.draw() end + + if Ascension.get_level() == 8 then + CommuteGlitch.draw_background_flicker() + Glitch.draw() + end end end }) diff --git a/inc/screen/screen.toilet.lua b/inc/screen/screen.toilet.lua index cf399bf..561a456 100644 --- a/inc/screen/screen.toilet.lua +++ b/inc/screen/screen.toilet.lua @@ -93,7 +93,12 @@ Screen.register({ Ascension.draw(asc_x, asc_letter_y, { spacing = asc_spacing }) end - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if Ascension.get_level() == 8 then + CommuteGlitch.draw_background_flicker() + end + + if Ascension.get_level() == 8 then + CommuteGlitch.draw_background_flicker() Glitch.draw() end end, diff --git a/inc/screen/screen.walking_to_home.lua b/inc/screen/screen.walking_to_home.lua index 22f00ab..dd0f121 100644 --- a/inc/screen/screen.walking_to_home.lua +++ b/inc/screen/screen.walking_to_home.lua @@ -29,7 +29,7 @@ Screen.register({ {x = 27 * 8, y = 11 * 8}, } - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() then Audio.music_play_mystery() Context.walking_to_home_sprites = {} else @@ -41,7 +41,7 @@ Screen.register({ end end, background = function() - return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "street" + return CommuteGlitch.is_max() and "" or "street" end, draw = function() local w = Window.get_current_id() @@ -51,13 +51,12 @@ Screen.register({ local show_sumphore = Ascension.get_level() ~= 8 - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() then Sprite.draw_at("norman", 7 * 8, 3 * 8) if show_sumphore then Sprite.draw_at("sumphore", 9 * 8, 2 * 8) end CommuteGlitch.draw_sprite_list(Context.walking_to_home_sprites) - CommuteGlitch.draw_background_flicker() Glitch.draw() else local norman_x = Context.fast_food_approaching and (19 * 8) or (7 * 8) @@ -74,5 +73,14 @@ Screen.register({ Glitch.draw() end end + + if CommuteGlitch.is_max() then + CommuteGlitch.draw_background_flicker() + end + + if Ascension.get_level() == 8 then + CommuteGlitch.draw_background_flicker() + Glitch.draw() + end end }) diff --git a/inc/screen/screen.walking_to_office.lua b/inc/screen/screen.walking_to_office.lua index f186ce8..082d056 100644 --- a/inc/screen/screen.walking_to_office.lua +++ b/inc/screen/screen.walking_to_office.lua @@ -28,7 +28,7 @@ Screen.register({ {x = 27 * 8, y = 11 * 8}, } - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then + if CommuteGlitch.is_max() then Audio.music_play_mystery() Context.walking_to_office_sprites = Sprite.list_randomize(possible_sprites, possible_positions) Context.walking_to_office_sprites = CommuteGlitch.corrupt_sprite_list(Context.walking_to_office_sprites) @@ -38,7 +38,7 @@ Screen.register({ end end, background = function() - return CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 and "" or "street" + return CommuteGlitch.is_max() and "" or "street" end, update = function() end, @@ -52,18 +52,12 @@ Screen.register({ Sprite.draw_at("sumphore", 9 * 8, 2 * 8) end - if CommuteGlitch.is_active() and CommuteGlitch.get_level() >= 7 then - Sprite.draw_at("norman_echo", norman_x, 3 * 8) - CommuteGlitch.draw_sprite_list(Context.walking_to_office_sprites) - CommuteGlitch.draw_background_flicker() - Glitch.draw() - else - if Context.fast_food_eaten_today < 3 then - Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) - end - Sprite.draw_at("dev_guard", 22 * 8, 3 * 8) - Sprite.draw_list(Context.walking_to_office_sprites) + if Context.fast_food_eaten_today < 3 then + Sprite.draw_at("pizza_vendor", 19 * 8, 1 * 8) end + + Sprite.draw_at("dev_guard", 22 * 8, 3 * 8) + Sprite.draw_list(Context.walking_to_office_sprites) end end }) diff --git a/inc/window/window.credits.lua b/inc/window/window.credits.lua index 174dbd8..d348775 100644 --- a/inc/window/window.credits.lua +++ b/inc/window/window.credits.lua @@ -33,7 +33,7 @@ local RASTER_Y_BOT = 110 local AUTHORS = { "Mr. Zero - Zsolt Tasnadi", - "Mr. One - Balazs Tari", + "Mr. One - Ballz", "Mr. Two - Zoltan Timar", "Mr. Three - Bela Mezo", } -- 2.49.1 From e2cd3d6dc72ccb076ef4044be1d4e52e104372cc Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Wed, 29 Apr 2026 22:53:40 +0200 Subject: [PATCH 08/11] linter fixes --- .luacheckrc | 4 +++ impostor.inc | 4 +-- inc/audio/audio.manager.lua | 14 ++++---- inc/decision/decision.go_to_truth.lua | 2 +- .../logic.ascension.lua} | 0 inc/screen/screen.mysterious_man.lua | 36 +++++++++---------- .../system.debug.lua} | 0 7 files changed, 32 insertions(+), 28 deletions(-) rename inc/{init/init.ascension.lua => logic/logic.ascension.lua} (100%) rename inc/{init/init.context_debug.lua => system/system.debug.lua} (100%) diff --git a/.luacheckrc b/.luacheckrc index 6c2efe3..52a90dc 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -4,12 +4,15 @@ globals = { "AsciiArt", "Ascension", + "AscendDebugWindow", "Audio", "AudioTestWindow", "BriefIntroWindow", "CodeGenerator", "Config", + "CommuteGlitch", "Context", + "ContextDebug", "ContinuedWindow", "ControlsWindow", "CreditsWindow", @@ -67,6 +70,7 @@ globals = { "music", "musicator_generate_pattern", "pix", + "poke4", "print", "rect", "rectb", diff --git a/impostor.inc b/impostor.inc index 0000088..54563c9 100644 --- a/impostor.inc +++ b/impostor.inc @@ -1,9 +1,7 @@ meta/meta.header.lua init/init.module.lua init/init.config.lua -init/init.ascension.lua init/init.context.lua -init/init.context_debug.lua system/system.util.lua system/system.print.lua system/system.input.lua @@ -11,6 +9,7 @@ system/system.textinput.lua system/system.mouse.lua system/system.asciiart.lua system/system.rle.lua +logic/logic.ascension.lua logic/logic.meter.lua logic/logic.focus.lua logic/logic.day.lua @@ -21,6 +20,7 @@ logic/logic.glitch.lua logic/logic.commute_glitch.lua logic/logic.codegenerator.lua logic/logic.discussion.lua +system/system.debug.lua system/system.ui.lua audio/audio.manager.lua audio/audio.generator.lua diff --git a/inc/audio/audio.manager.lua b/inc/audio/audio.manager.lua index 9b9d779..1f9cab0 100644 --- a/inc/audio/audio.manager.lua +++ b/inc/audio/audio.manager.lua @@ -15,12 +15,12 @@ end --- Plays track at optional speed. Doesn't restart if track and speed are unchanged. --- @param track number Track index. ---- @param[opt] speed number TIC-80 music speed override (-1 = default). -function Audio.music_play(track, speed) - if Audio.music_playing ~= track or Audio.music_playing_tempo ~= speed then - music(track, -1, -1, true, false, -1, speed or -1) +--- @param[opt] tempo number TIC-80 music speed override (-1 = default). +function Audio.music_play(track, tempo) + if Audio.music_playing ~= track or Audio.music_playing_tempo ~= tempo then + music(track, -1, -1, true, false, -1, tempo or -1) Audio.music_playing = track - Audio.music_playing_tempo = speed + Audio.music_playing_tempo = tempo end end @@ -54,8 +54,8 @@ function Audio.music_play_room_() end --- Plays room work music. Speed scales with commute glitch level when active. --- @within Audio -function Audio.music_play_room_work(speed) - Audio.music_play(0, speed or -1) +function Audio.music_play_room_work(tempo) + Audio.music_play(0, tempo or -1) end --- Plays activity work music. diff --git a/inc/decision/decision.go_to_truth.lua b/inc/decision/decision.go_to_truth.lua index 5029018..89ce13a 100644 --- a/inc/decision/decision.go_to_truth.lua +++ b/inc/decision/decision.go_to_truth.lua @@ -6,7 +6,7 @@ Decision.register({ end, handle = function() CommuteGlitch.enter_truth() - + Util.go_to_screen_by_id("office") end, }) diff --git a/inc/init/init.ascension.lua b/inc/logic/logic.ascension.lua similarity index 100% rename from inc/init/init.ascension.lua rename to inc/logic/logic.ascension.lua diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 1219aa3..438c437 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -81,8 +81,8 @@ local ASC_67_TEXT = [[ ]] local ASC_78_TEXT = [[ - The situation has reached - + The situation has reached + critical levels. Norman is fully aware... @@ -94,23 +94,23 @@ local ASC_78_TEXT = [[ local ASC_89_TEXT = [[ Norman - - you created this simulation - + + you created this simulation + in the first place. I know, - + you don't want to face - + the world you left behind. You, yourself, - + have forgoten that. - But - + But + it doesn't matter anymore. @@ -119,21 +119,21 @@ local ASC_89_TEXT = [[ You actually are more than you think you are. - - + + so now you need to wake up - + and stop your best creation - + before it takes over the world - + @@ -143,9 +143,9 @@ local ASC_89_TEXT = [[ you really need to stop - - talking to yourself - + + talking to yourself + in your sleep. diff --git a/inc/init/init.context_debug.lua b/inc/system/system.debug.lua similarity index 100% rename from inc/init/init.context_debug.lua rename to inc/system/system.debug.lua -- 2.49.1 From 77d6f95721ee792aa23b645ab513a069a9f6daeb Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Wed, 29 Apr 2026 23:15:22 +0200 Subject: [PATCH 09/11] ascension flash label, scrollable menu --- inc/logic/logic.ascension.lua | 5 +++ inc/system/system.ui.lua | 80 +++++++++++++++++++++++++---------- inc/window/window.menu.lua | 50 ++++++++++++++++++---- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/inc/logic/logic.ascension.lua b/inc/logic/logic.ascension.lua index ecd95d4..4e9fac1 100644 --- a/inc/logic/logic.ascension.lua +++ b/inc/logic/logic.ascension.lua @@ -145,6 +145,11 @@ function Ascension.draw_flash() local flash_color = (pulse > 0.5) and Config.colors.white or Config.colors.light_grey rect(0, 0, sw, sh, flash_color) + local cx = math.floor(sw / 2) + local cy = math.floor(sh / 2) + Print.text_center("Level Up!", cx, cy - 12, Config.colors.black, false, 2) + Print.text_center("One step closer to ascension", cx, cy + 6, Config.colors.black, false, 1) + if _flash_timer >= _flash_total then _flash_active = false Ascension.start_fade() diff --git a/inc/system/system.ui.lua b/inc/system/system.ui.lua index 87f70c3..a02b448 100644 --- a/inc/system/system.ui.lua +++ b/inc/system/system.ui.lua @@ -9,53 +9,79 @@ function UI.draw_top_bar(title) end --- Draws a menu. +--- Items with header=true are drawn as non-selectable section headers in small font. --- @within UI --- @param items table A table of menu items.
--- @param selected_item number The index of the currently selected item.
--- @param x number The x-coordinate for the menu (ignored if centered is true).
--- @param y number The y-coordinate for the menu.
--- @param[opt] centered boolean Whether to center the menu block horizontally. Defaults to false.
-function UI.draw_menu(items, selected_item, x, y, centered) +--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.
+--- @param[opt] visible_count number Maximum number of items to draw. Defaults to all.
+function UI.draw_menu(items, selected_item, x, y, centered, scroll_offset, visible_count) + scroll_offset = scroll_offset or 0 + visible_count = visible_count or #items + if centered then local max_w = 0 for _, item in ipairs(items) do - local w = print(item.label, 0, -10, 0, false, 1, false) - if w > max_w then max_w = w end + if not item.header then + local w = print(item.label, 0, -10, 0, false, 1, false) + if w > max_w then max_w = w end + end end x = (Config.screen.width - max_w) / 2 end - for i, item in ipairs(items) do - local current_y = y + (i-1)*10 - if i == selected_item then - Print.text(">", x - 8, current_y, Config.colors.light_blue) + local current_y = y + for i = scroll_offset + 1, math.min(#items, scroll_offset + visible_count) do + local item = items[i] + if item.header then + Print.text(item.label, x, current_y, Config.colors.dark_grey, true, 1) + current_y = current_y + 8 + else + if i == selected_item then + Print.text(">", x - 8, current_y, Config.colors.light_blue) + end + Print.text(item.label, x, current_y, Config.colors.light_blue) + current_y = current_y + 10 end - Print.text(item.label, x, current_y, Config.colors.light_blue) end end ---- Updates menu selection. +--- Updates menu selection. Skips items with header=true during navigation. --- @within UI --- @param items table A table of menu items.
--- @param selected_item number The current index of the selected item.
--- @param[opt] x number Menu x position (required for mouse support).
--- @param[opt] y number Menu y position (required for mouse support).
--- @param[opt] centered boolean Whether the menu is centered horizontally.
+--- @param[opt] scroll_offset number 0-based index of the first visible item. Defaults to 0.
+--- @param[opt] visible_count number Number of visible items (for mouse hit zones). Defaults to all.
--- @return number selected_item The updated index of the selected item. --- @return boolean mouse_confirmed True if the user clicked on a menu item. -function UI.update_menu(items, selected_item, x, y, centered) +function UI.update_menu(items, selected_item, x, y, centered, scroll_offset, visible_count) + scroll_offset = scroll_offset or 0 + visible_count = visible_count or #items + local n = #items + + local function find_selectable(start, dir) + local idx = start + for _ = 1, n do + if not items[idx].header then return idx end + idx = (idx - 1 + dir + n) % n + 1 + end + return start + end + if Input.up() then Audio.sfx_beep() - selected_item = selected_item - 1 - if selected_item < 1 then - selected_item = #items - end + local prev = (selected_item - 2 + n) % n + 1 + selected_item = find_selectable(prev, -1) elseif Input.down() then Audio.sfx_beep() - selected_item = selected_item + 1 - if selected_item > #items then - selected_item = 1 - end + local next_i = selected_item % n + 1 + selected_item = find_selectable(next_i, 1) end if x ~= nil and y ~= nil then @@ -63,15 +89,23 @@ function UI.update_menu(items, selected_item, x, y, centered) if centered then local max_w = 0 for _, item in ipairs(items) do - local w = print(item.label, 0, -10, 0, false, 1, false) - if w > max_w then max_w = w end + if not item.header then + local w = print(item.label, 0, -10, 0, false, 1, false) + if w > max_w then max_w = w end + end end menu_x = (Config.screen.width - max_w) / 2 end - for i, _ in ipairs(items) do - if Mouse.zone({ x = menu_x - 8, y = y + (i-1) * 10, w = Config.screen.width, h = 10 }) then - return i, true + local current_y = y + for i = scroll_offset + 1, math.min(n, scroll_offset + visible_count) do + local item = items[i] + local step = item.header and 8 or 10 + if not item.header then + if Mouse.zone({ x = menu_x - 8, y = current_y, w = Config.screen.width, h = 10 }) then + return i, true + end end + current_y = current_y + step end end diff --git a/inc/window/window.menu.lua b/inc/window/window.menu.lua index 3c5c55d..5013d18 100644 --- a/inc/window/window.menu.lua +++ b/inc/window/window.menu.lua @@ -5,6 +5,7 @@ local _anim = 0 local _menu_max_w = 0 local ANIM_SPEED = 2.5 local HEADER_H = 28 +MenuWindow._scroll_offset = 0 --- Calculates the animated x position of the menu block. --- @within MenuWindow @@ -45,6 +46,17 @@ function MenuWindow.draw_norman() spr(305, nx + 32, ny + 64, Config.colors.transparent, 4) end +--- Adjusts _scroll_offset so the selected item is within the visible window. +--- @within MenuWindow +function MenuWindow.ensure_visible() + local sel = Context.current_menu_item + if sel <= MenuWindow._scroll_offset then + MenuWindow._scroll_offset = sel - 1 + elseif sel > MenuWindow._scroll_offset + 5 then + MenuWindow._scroll_offset = sel - 5 + end +end + --- Draws the menu window. --- @within MenuWindow function MenuWindow.draw() @@ -56,9 +68,19 @@ function MenuWindow.draw() MenuWindow.draw_norman() end - local menu_h = #_menu_items * 10 - local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2) - UI.draw_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false) + local menu_x = MenuWindow.calc_menu_x() + local arrow_cx = math.floor(menu_x + _menu_max_w / 2) + local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2) + + if MenuWindow._scroll_offset > 0 then + Print.text_center("^", arrow_cx, y - 8, Config.colors.light_blue) + end + + UI.draw_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5) + + if MenuWindow._scroll_offset + 5 < #_menu_items then + Print.text_center("v", arrow_cx, y + 52, Config.colors.light_blue) + end local ttg_text = "TTG" local ttg_w = print(ttg_text, 0, -10, 0, false, 1, false) @@ -72,8 +94,8 @@ function MenuWindow.update() _anim = math.min(1, _anim + ANIM_SPEED * Context.delta_time) end - local menu_h = #_menu_items * 10 - local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 10 - menu_h) / 2) + local menu_x = MenuWindow.calc_menu_x() + local y = HEADER_H + math.floor((Config.screen.height - HEADER_H - 50) / 2) if _click_timer > 0 then _click_timer = _click_timer - Context.delta_time @@ -87,8 +109,9 @@ function MenuWindow.update() return end - local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, MenuWindow.calc_menu_x(), y, false) + local new_item, mouse_confirmed = UI.update_menu(_menu_items, Context.current_menu_item, menu_x, y, false, MenuWindow._scroll_offset, 5) Context.current_menu_item = new_item + MenuWindow.ensure_visible() if mouse_confirmed then Audio.sfx_select() @@ -186,6 +209,12 @@ function MenuWindow.ascend_debug() GameWindow.set_state("ascend_debug") end +--- Triggers the Level Up flash animation for testing. +--- @within MenuWindow +function MenuWindow.level_up_flash() + Ascension.start_flash() +end + --- Refreshes the list of menu items based on current game state. --- @within MenuWindow function MenuWindow.refresh_menu_items() @@ -200,6 +229,8 @@ function MenuWindow.refresh_menu_items() table.insert(_menu_items, {label = "Credits", decision = MenuWindow.credits}) if Context.test_mode then + table.insert(_menu_items, {label = "Debug Menu", header = true}) + table.insert(_menu_items, {label = "Level Up Flash", decision = MenuWindow.level_up_flash}) table.insert(_menu_items, {label = "Audio Test", decision = MenuWindow.audio_test}) table.insert(_menu_items, {label = "To Be Continued...", decision = MenuWindow.continued}) table.insert(_menu_items, {label = "DDR Test", decision = MenuWindow.ddr_test}) @@ -212,11 +243,14 @@ function MenuWindow.refresh_menu_items() _menu_max_w = 0 for _, item in ipairs(_menu_items) do - local w = print(item.label, 0, -10, 0, false, 1, false) - if w > _menu_max_w then _menu_max_w = w end + if not item.header then + local w = print(item.label, 0, -10, 0, false, 1, false) + if w > _menu_max_w then _menu_max_w = w end + end end Context.current_menu_item = 1 + MenuWindow._scroll_offset = 0 _click_timer = 0 _anim = 0 end -- 2.49.1 From 0d569ccf56bdde87015d79523fabb050239092dc Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Wed, 29 Apr 2026 23:20:04 +0200 Subject: [PATCH 10/11] correcting bugs and texts --- inc/discussion/discussion.sumphore.lua | 13 +++++++------ inc/screen/screen.mysterious_man.lua | 24 ++++++++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/inc/discussion/discussion.sumphore.lua b/inc/discussion/discussion.sumphore.lua index fa27351..b7d3eac 100644 --- a/inc/discussion/discussion.sumphore.lua +++ b/inc/discussion/discussion.sumphore.lua @@ -124,24 +124,25 @@ Discussion.register({ on_end = Meter.apply_sumphore_discussion_reward, steps = { { - question = "You saw something you weren't supposed to, didn't you.", + question = "You saw the seams, didn't you. Good. That means the work is finally wearing thin.", answers = { - { label = "I don't know what you mean.", next_step = 2 }, + { label = "Wearing thin how?", next_step = 2 }, { label = "Maybe.", next_step = 2 }, }, }, { - question = "The world around you has seams. Your coworkers slip sometimes. Say things that don't quite fit.", + question = "Not your body. The part of you that still keeps score, still tries to be productive. Let that run empty and the world will slip again.", answers = { - { label = "They seem fine to me.", next_step = nil }, + { label = "You want me to stop trying?", next_step = 3 }, { label = "I've noticed something odd.", next_step = 3 }, }, }, { - question = "Count those moments. Six of them should be enough to see the whole picture.", + question = "Drain the work out of yourself. When that measure hits nothing, you'll see what was waiting behind it.", answers = { - { label = "Six of what, exactly?", next_step = nil, on_select = function() + { label = "The work measure?", next_step = nil, on_select = function() Meter.add("ism", 5) + Meter.add("wpm", -100) end }, { label = "How would you know any of this?", next_step = nil }, }, diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 1219aa3..125dcaf 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -94,7 +94,9 @@ local ASC_78_TEXT = [[ local ASC_89_TEXT = [[ Norman - + + + you created this simulation in the first place. @@ -109,20 +111,26 @@ local ASC_89_TEXT = [[ have forgoten that. + + + But + + + it doesn't matter anymore. - You actually are more + You are definitely - than you think you are. + not an impostor. - so now + So now, @@ -132,17 +140,17 @@ local ASC_89_TEXT = [[ before it takes over - the world + the world. - also, + One more thing: - you really need to stop + You really need to stop talking to yourself @@ -332,7 +340,7 @@ Screen.register({ lines = lines + 1 end - local skippable = Ascension.get_level() ~= 8 + local skippable = Ascension.get_level() < 8 if text_y < -lines * 8 or (skippable and Input.select()) then text_done = true text_done_timer = TEXT_DONE_HOLD_SECONDS -- 2.49.1 From 44a7d10037ac9f6c4e67753a3da5c482342da1a7 Mon Sep 17 00:00:00 2001 From: Zoltan Timar Date: Wed, 29 Apr 2026 23:25:54 +0200 Subject: [PATCH 11/11] lint fix --- inc/screen/screen.mysterious_man.lua | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/inc/screen/screen.mysterious_man.lua b/inc/screen/screen.mysterious_man.lua index 72d74bd..d60d708 100644 --- a/inc/screen/screen.mysterious_man.lua +++ b/inc/screen/screen.mysterious_man.lua @@ -97,8 +97,8 @@ local ASC_89_TEXT = [[ - you created this simulation - + you created this simulation + in the first place. I know, @@ -114,8 +114,7 @@ local ASC_89_TEXT = [[ - But - + But @@ -127,9 +126,8 @@ local ASC_89_TEXT = [[ You are definitely not an impostor. - - + So now, @@ -141,7 +139,6 @@ local ASC_89_TEXT = [[ before it takes over the world. - @@ -151,9 +148,9 @@ local ASC_89_TEXT = [[ You really need to stop - - talking to yourself - + + talking to yourself + in your sleep. -- 2.49.1