@@ -207,12 +207,32 @@ func (m *menu) workDir(sub string) string {
207207 return d
208208}
209209
210+ // gameEnv is the minimal environment handed to a sandboxed game. It does NOT
211+ // inherit the daemon's environment (which carries operator secrets like
212+ // COINPAY_API_KEY) — a third-party game binary has no business seeing those.
213+ // TERM comes from the client PTY so ncurses games (Space Invaders, Pac-Man,
214+ // Tetris, Moon Patrol) can open the terminal; without it initscr() fails with
215+ // "Error opening terminal" and the game exits before drawing a frame.
216+ func (m * menu ) gameEnv (work string ) []string {
217+ term := m .ctx .Term
218+ if term == "" {
219+ term = "xterm-256color" // sane default if the client didn't request a PTY type
220+ }
221+ return []string {
222+ "TERM=" + term ,
223+ "PATH=/usr/games:/usr/local/games:/usr/local/bin:/usr/bin:/bin" ,
224+ "HOME=" + work ,
225+ "LANG=C.UTF-8" , // unicode box-drawing for the ncurses games
226+ }
227+ }
228+
210229// launchDoom suspends the TUI and bridges the session to a sandboxed
211230// doom-ascii on a real PTY. Savegames land in the per-user work dir.
212231func (m * menu ) launchDoom (wad string ) tea.Cmd {
213232 bin := doomBin (m .ctx )
214233 work := m .workDir (filepath .Join ("doom" , strings .TrimSuffix (filepath .Base (wad ), filepath .Ext (wad ))))
215234 cmd := m .ctx .Sandbox .Command (work , bin , "-iwad" , wad )
235+ cmd .Env = m .gameEnv (work )
216236 return tea .Exec (newPtyExec (cmd , m .width , m .height ), func (err error ) tea.Msg {
217237 return gameDoneMsg {name : "DOOM" , err : err }
218238 })
@@ -228,6 +248,7 @@ func (m *menu) launchExt(g extGame) tea.Cmd {
228248 }
229249 work := m .workDir (g .id )
230250 cmd := m .ctx .Sandbox .Command (work , bin , g .args ... )
251+ cmd .Env = m .gameEnv (work )
231252 return tea .Exec (newPtyExec (cmd , m .width , m .height ), func (err error ) tea.Msg {
232253 return gameDoneMsg {name : g .label , err : err }
233254 })
0 commit comments