The port installed handlers for SIGHUP and SIGTERM only, so SIGINT and SIGQUIT killed the process with tcell still holding the tty and dropped the user into a shell with no echo and a scrambled screen. All four signals now go to one os/signal channel read by one goroutine, and every path calls Terminal.Fini before os.Exit(0) -- C's leave(), "leave quickly but curteously" (main.c). The save decision, written into the savesOnSignal comment: SIGHUP and SIGTERM keep autosaving; SIGINT and SIGQUIT restore and exit without saving. No path in C saves on INT or QUIT (leave() is endwin-and-exit, quit() confirms/scores/exits, endit() goes through fatal(), and save.c auto_save is reserved for HUP/TERM), the semantics agree (involuntary teardown is worth rescuing a game from; a deliberate "stop now" must not become a one-keystroke checkpoint against an anti-save-scum save discipline), and it is the safe choice, since AutoSave gob-encodes live state the main goroutine is still mutating after removing the old file. One reader of one signal is also what closes the corruption window: a second signal arriving while a SIGHUP's AutoSave is mid-write stays unread in the buffer instead of exiting out from under the writer. cmd/rogue/main_test.go covers the ordering per signal, the save/no-save split, the mid-save second-signal interleaving, and real SIGINT/SIGQUIT/SIGHUP/SIGTERM delivered to the test process through the same notifySignals wiring the game uses. Two premises behind the report were wrong and are recorded rather than silently fixed: leave() is not installed on SIGINT/SIGQUIT during play (the wiring is in mdport.c; the shipped build calls md_onsignal_default and installs nothing, and leave() appears only in the endgame paths of rip.c and main.c), and Ctrl-C never generated SIGINT here anyway, since tcell's raw mode clears ISIG and the key arrives as byte 0x03 -- as it did in C, whose setup() calls curses raw(). The real exposure is kill -INT / kill -QUIT and the window before term.New(). ARCHITECTURE.md section 9 gains rows for SIGTSTP/tstp() (deliberately dropped: raw mode means Ctrl-Z cannot reach the process, suspending the screen from the signal goroutine would race the drawing goroutine, and C armed tstp only after a successful restore(); the ! shell escape covers the need), for SIGINT not routing to the interactive quit() prompt, and for auto_save on the fault signals. Section 5.3's claim that tcell handles SIGTSTP was false -- tcell registers only SIGWINCH -- and is corrected.
5.9 KiB
5.9 KiB