aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-01 14:38:08 -0400
committerhistoria <[not public]>2026-10-01 14:38:08 -0400
commit1082f3336085c15992ca83d5553c0ba52208d70a (patch)
tree4fcedaa90bc01c41456258a9d57f492f31754948 /internal/game
parentd4eae333a8d8a96452bbf3617aa85b50466c11a7 (diff)
downloadthehouseoficarus-1082f3336085c15992ca83d5553c0ba52208d70a.tar.gz
feat: pai gow poker added to the casino
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/act_burn.go2
-rw-r--r--internal/game/casino.go12
-rw-r--r--internal/game/casino_ux.go2
-rw-r--r--internal/game/cmd_registry.go13
-rw-r--r--internal/game/hacking/liars_dice_test.go2
-rw-r--r--internal/game/hardening_tick_lifecycle_test.go2
-rw-r--r--internal/game/money.go4
-rw-r--r--internal/game/pai_gow_test.go141
-rw-r--r--internal/game/render_joker.go37
-rw-r--r--internal/game/render_pai_gow.go79
-rw-r--r--internal/game/render_roulette.go3
-rw-r--r--internal/game/sys_triggers.go2
-rw-r--r--internal/game/tick_systems.go4
-rw-r--r--internal/game/tick_systems_test.go8
-rw-r--r--internal/game/wallet_test.go2
15 files changed, 298 insertions, 15 deletions
diff --git a/internal/game/act_burn.go b/internal/game/act_burn.go
index 877fd78..706d0a7 100644
--- a/internal/game/act_burn.go
+++ b/internal/game/act_burn.go
@@ -424,7 +424,7 @@ func (g *Game) broadcastDrop(p *player.Player, itemID, itemName string) {
func (g *Game) FireTick() {
// Mandatory tick-system guard: the master per-tick subscriber can fire
// before SetHub during startup, and burning out a fire reaches
- // cancelStokers -> g.Hub.PlayersInRoom. See building_guide/tick_order.md.
+ // cancelStokers -> g.Hub.PlayersInRoom. See dev_guide/tick_order.md.
if g.Hub == nil {
return
}
diff --git a/internal/game/casino.go b/internal/game/casino.go
index d433f2a..e6c0f27 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -726,6 +726,8 @@ func (g *Game) renderCasinoSnapshot(sess *net.Session, snapshot casino.Snapshot)
// how verbose the command menus are.
size := sess.Player.OptionInt("card_size")
switch s := snapshot.(type) {
+ case *casino.PaiGowSnapshot:
+ return renderPaiGow(s, mode, unicode, size, sess.Player.OptionString("card_style"), g.CardArt)
case *casino.VideoPokerSnapshot:
return renderVideoPoker(s, mode, unicode, size, sess.Player.OptionString("card_style"), g.CardArt)
case *casino.CrapsSnapshot:
@@ -904,7 +906,7 @@ func cardRenderSize(unicode bool, size int) int {
return 7
}
-var suitSymbols = map[string]string{"hearts": "♥", "spades": "♠", "diamonds": "♦", "clubs": "♣"}
+var suitSymbols = map[string]string{"hearts": "♥", "spades": "♠", "diamonds": "♦", "clubs": "♣", "joker": "★"}
func cardSymbol(card casino.CardView) string {
if card.Hidden {
@@ -917,7 +919,13 @@ func cardArtLines(card casino.CardView, size int, unicode bool, style string, ar
if card.Hidden {
return generatedCardArt("?", "", size, unicode, style)
}
- key := card.Rank + strings.ToUpper(card.Suit[:1])
+ key := card.Rank
+ if card.Rank == "JK" {
+ return jokerCardArt(size, unicode, style, art)
+ }
+ if card.Suit != "" {
+ key += strings.ToUpper(card.Suit[:1])
+ }
if configured := art.Art(key, size); len(configured) == size {
if unicode && style == "light" {
// The shared pip art draws a dark box frame, which leaves the top
diff --git a/internal/game/casino_ux.go b/internal/game/casino_ux.go
index fc37e7d..6f7964f 100644
--- a/internal/game/casino_ux.go
+++ b/internal/game/casino_ux.go
@@ -79,6 +79,8 @@ func (g *Game) casinoChoices(roomID int) []casinoChoice {
names = append(names, "bj")
case casino.GameBaccarat:
names = append(names, "bac")
+ case casino.GamePaiGow:
+ names = append(names, "paigow", "pai", "pai gow", "pai gow poker")
}
choices = append(choices, casinoChoice{address: string(c.Game), names: names, table: true})
}
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index fbb401c..d8f6b76 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -223,6 +223,9 @@ func classifyCommand(cmd string) CommandClass {
if _, ok := casino.ParsePokerDigits(cmd); ok {
return ClassInstant
}
+ if _, ok := casino.ParsePaiGowDigits(cmd); ok {
+ return ClassInstant
+ }
if def, ok := commandRegistry[cmd]; ok {
return def.class
}
@@ -293,6 +296,16 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
}
}
+ // Pai Gow owns these inputs only while seated; leave normal commands alone.
+ if p != nil && g.Casino != nil {
+ if t, playing := g.Casino.Participation(p.Name); playing && t != nil && t.Config.Game == casino.GamePaiGow {
+ numeric := cmd != "" && cmd[0] >= '0' && cmd[0] <= '9'
+ if numeric || cmd == "set" || cmd == "house" || cmd == "settle" {
+ g.executeTableAction(sess, strings.Join(append([]string{cmd}, args...), " "))
+ return
+ }
+ }
+ }
// Video poker: numeric tokens toggle holds and d draws, so both are
// intercepted before the shared registry (where d means "down").
_, pokerDigits := casino.ParsePokerDigits(cmd)
diff --git a/internal/game/hacking/liars_dice_test.go b/internal/game/hacking/liars_dice_test.go
index 59ebb92..83bed93 100644
--- a/internal/game/hacking/liars_dice_test.go
+++ b/internal/game/hacking/liars_dice_test.go
@@ -10,7 +10,7 @@ import (
// "illegal use of non-zero Builder copied by value" on ordinary input — a
// forced challenge resolves on the very first bid. A panic here runs on the
// game owner goroutine, which has no recovery, so it terminated the server.
-// See building_guide/concurrency.md for the owner's no-recovery guarantee.
+// See dev_guide/concurrency.md for the owner's no-recovery guarantee.
func TestLiarsDiceForcedChallengeDoesNotPanic(t *testing.T) {
g := NewLiarsDiceGame()
diff --git a/internal/game/hardening_tick_lifecycle_test.go b/internal/game/hardening_tick_lifecycle_test.go
index b5b3bb9..d16c2c8 100644
--- a/internal/game/hardening_tick_lifecycle_test.go
+++ b/internal/game/hardening_tick_lifecycle_test.go
@@ -51,7 +51,7 @@ func TestFireTickNilHubIsSafe(t *testing.T) {
// TestSequenceTickFiresInDeterministicOrder guards the sorted iteration over
// g.sequences: map iteration randomly chose which simultaneous sequence
// advanced first, contradicting the determinism contract in
-// building_guide/tick_order.md.
+// dev_guide/tick_order.md.
func TestSequenceTickFiresInDeterministicOrder(t *testing.T) {
g := escapeTestGame()
g.sequences = make(map[string]*sequence)
diff --git a/internal/game/money.go b/internal/game/money.go
index 35355ca..4c3316e 100644
--- a/internal/game/money.go
+++ b/internal/game/money.go
@@ -7,7 +7,7 @@ package game
// still listed as pending, because a listed stake means the process died
// before the round resolved. Two-phase journal recovery was deliberately
// removed as out of scope for a game this size; see
-// building_guide/concurrency.md ("Durable money") before reintroducing it.
+// dev_guide/concurrency.md ("Durable money") before reintroducing it.
import (
"crypto/rand"
@@ -128,7 +128,7 @@ func addCreditsChecked(a, b int) (int, error) {
// same save that settled them.
//
// This is intentionally the entire recovery story. Two-phase journal recovery
-// is out of scope for this game (see building_guide/concurrency.md).
+// is out of scope for this game (see dev_guide/concurrency.md).
func (g *Game) recoverPendingWagers() {
names, err := g.AccountStore.ListCharacterNames()
if err != nil {
diff --git a/internal/game/pai_gow_test.go b/internal/game/pai_gow_test.go
new file mode 100644
index 0000000..614d0dd
--- /dev/null
+++ b/internal/game/pai_gow_test.go
@@ -0,0 +1,141 @@
+package game
+
+import (
+ "strings"
+ "testing"
+ "unicode/utf8"
+
+ "thehouseoficarus/internal/cardart"
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/world"
+)
+
+func TestPaiGowCommandsLookAndSettlement(t *testing.T) {
+ g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: pai_gow\n pai_gow:\n envy: true\n")
+ g.executePlay(sess, []string{"pai"}, "play pai")
+ table, playing := g.Casino.Participation(sess.Player.Name)
+ if !playing || table.Config.Game != casino.GamePaiGow {
+ t.Fatal("alias failed")
+ }
+ conn.data = nil
+ g.executeTableStatus(sess, []string{"rules"}, "table rules")
+ text := stripANSI(string(conn.data))
+ for _, want := range []string{"FORTUNE", "ENVY (FIXED)", "750:1", "Three pair"} {
+ if !strings.Contains(text, want) {
+ t.Fatalf("missing %s: %s", want, text)
+ }
+ }
+ g.executeBet(sess, []string{"20"}, "bet 20")
+ g.executeBet(sess, []string{"5", "fortune"}, "bet 5 fortune")
+ g.executeTableAction(sess, "deal")
+ snap := table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot)
+ if len(snap.Cards) != 7 || len(snap.DealerHigh) != 0 || len(sess.Player.PendingWagers) != 2 {
+ t.Fatal("deal/side bet")
+ }
+ conn.data = nil
+ g.executeCommand(sess, "22", nil, "22")
+ if text = string(conn.data); !strings.Contains(text, "different numbers") {
+ t.Fatal(text)
+ }
+ if classifyCommand("67") != ClassInstant || classifyCommand("13456") != ClassInstant {
+ t.Fatal("numeric classification")
+ }
+ // A numeric selection either locks a valid split or gives a clear foul;
+ // either way it must reach Pai Gow rather than the generic unknown verb.
+ conn.data = nil
+ g.executeCommand(sess, "67", nil, "67")
+ text = string(conn.data)
+ if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase == "setting" && !strings.Contains(text, "FOUL HAND") {
+ t.Fatal(text)
+ }
+ g.executeCommand(sess, "house", nil, "house")
+ if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase != "waiting" || len(sess.Player.PendingWagers) != 0 {
+ t.Fatal("settlement did not finish")
+ }
+}
+func TestPaiGowFeltAndJokerAllModes(t *testing.T) {
+ store := cardart.Load("../../data/cards.yaml")
+ for _, size := range []int{1, 5, 7, 9} {
+ if len(store.Art("JK", size)) != size {
+ t.Fatalf("missing JK art at %d", size)
+ }
+ for _, unicode := range []bool{false, true} {
+ for _, style := range []string{"dark", "light"} {
+ for _, art := range []*cardart.Store{nil, store} {
+ lines := jokerCardArt(size, unicode, style, art)
+ if len(lines) != size {
+ t.Fatal(size, lines)
+ }
+ for _, line := range lines {
+ if size > 1 && utf8.RuneCountInString(line) != size {
+ t.Fatalf("%d width: %q", size, line)
+ }
+ if !unicode {
+ for _, r := range line {
+ if r > 127 {
+ t.Fatalf("ASCII Joker has Unicode: %q", line)
+ }
+ }
+ }
+ }
+ for _, mode := range []string{"none", "xterm256", "truecolor"} {
+ out := cardsLine(mode, []casino.CardView{{Rank: "JK", Suit: "joker"}}, cardRenderSize(unicode, size), unicode, style, art)
+ if !strings.Contains(strings.Join(out, "\n"), "J") {
+ t.Fatal(out)
+ }
+ }
+ }
+ }
+ }
+ }
+ for _, envy := range []bool{false, true} {
+ for _, unicode := range []bool{false, true} {
+ cfg := casino.TableConfig{Game: casino.GamePaiGow, PaiGow: casino.PaiGowConfig{Envy: envy}}
+ felt := renderPaiGowFelt(cfg, "none", unicode)
+ lines := strings.Split(strings.TrimRight(felt, "\n"), "\n")
+ width := utf8.RuneCountInString(lines[0])
+ for _, line := range lines {
+ if utf8.RuneCountInString(line) != width {
+ t.Fatal("jagged felt", line)
+ }
+ }
+ if !strings.Contains(felt, "Three pair") || !strings.Contains(felt, "1000:1") {
+ t.Fatal(felt)
+ }
+ if !envy && (!strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "30:1")) {
+ t.Fatal(felt)
+ }
+ if cfg.PaiGow.Envy && (strings.Contains(felt, "ENVY OFF") || !strings.Contains(felt, "ENVY (FIXED)")) {
+ t.Fatal(felt)
+ }
+ }
+ }
+ cfg := casino.TableConfig{MinBet: 5, MaxBet: 1000, Game: casino.GamePaiGow, PaiGow: casino.PaiGowConfig{Variant: casino.PaiGowFaceUp, PushAceHigh: "5_15_40"}}
+ felt := renderPaiGowFelt(cfg, "none", true)
+ if !strings.Contains(felt, "DEALER FACE UP") || !strings.Contains(felt, "PUSH ACE HIGH 5/15/40:1") {
+ t.Fatal(felt)
+ }
+}
+func TestVestaPaiGowRooms(t *testing.T) {
+ _, rooms, _ := loadVesta(t)
+ for id, variant := range map[int]string{6195: casino.PaiGowClassic, 6196: casino.PaiGowEZ, 6197: casino.PaiGowNoPush, 6198: casino.PaiGowFaceUp} {
+ r, ok := rooms[id]
+ if !ok || len(r.CasinoTables) != 1 {
+ t.Fatalf("missing Pai Gow room %d", id)
+ }
+ cfg := r.CasinoTables[0]
+ if cfg.Validate() != nil || cfg.Game != casino.GamePaiGow || cfg.PaiGow.Variant != variant || cfg.PaiGow.Envy != (id != 6197) {
+ t.Fatalf("bad Pai Gow configuration: %+v", cfg)
+ }
+ }
+ if rooms[6192].Exits[world.East].Room != 6195 || rooms[6195].Exits[world.West].Room != 6192 || rooms[6195].Exits[world.North].Room != 6196 || rooms[6196].Exits[world.South].Room != 6195 || rooms[6195].Exits[world.South].Room != 6197 || rooms[6197].Exits[world.North].Room != 6195 || rooms[6196].Exits[world.North].Room != 6198 || rooms[6198].Exits[world.South].Room != 6196 {
+ t.Fatal("broken Pai Gow navigation")
+ }
+}
+func TestJokerEmptySuitDoesNotPanic(t *testing.T) {
+ for _, size := range []int{5, 7, 9} {
+ if len(cardArtLines(casino.CardView{Rank: "JK"}, size, true, "dark", nil)) != size {
+ t.Fatal(size)
+ }
+ }
+}
diff --git a/internal/game/render_joker.go b/internal/game/render_joker.go
new file mode 100644
index 0000000..821f9f4
--- /dev/null
+++ b/internal/game/render_joker.go
@@ -0,0 +1,37 @@
+package game
+
+import (
+ "strings"
+ "thehouseoficarus/internal/cardart"
+)
+
+// Dedicated Joker face in every supported size, also usable without cards.yaml.
+// Never reuse Unicode store art for ASCII-only clients.
+func jokerCardArt(size int, unicode bool, style string, store *cardart.Store) []string {
+ if size == 1 {
+ return []string{"JK"}
+ }
+ var lines []string
+ if unicode {
+ lines = store.Art("JK", size)
+ }
+ if len(lines) != size {
+ lines = generatedCardArt("JK", "", size, unicode, style)
+ motifs := map[int][]string{5: {" * "}, 7: {" ^ ^ ", "(o o)", " v "}, 9: {" /\u005c /\u005c ", " o o ", " <J> ", " \\_/ ", " * "}}
+ if motif, ok := motifs[size]; ok {
+ for i, text := range motif {
+ left, right := "|", "|"
+ if unicode {
+ left, right = "│", "│"
+ }
+ lines[2+i] = left + text + right
+ }
+ }
+ }
+ lines = append([]string(nil), lines...)
+ if unicode && style == "light" {
+ lines[0] = strings.Repeat("▄", size)
+ lines[size-1] = strings.Repeat("▀", size)
+ }
+ return lines
+}
diff --git a/internal/game/render_pai_gow.go b/internal/game/render_pai_gow.go
new file mode 100644
index 0000000..2005daf
--- /dev/null
+++ b/internal/game/render_pai_gow.go
@@ -0,0 +1,79 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/cardart"
+ "thehouseoficarus/internal/casino"
+)
+
+func renderPaiGowFelt(cfg casino.TableConfig, mode string, unicode bool) string {
+ c := newCrapsCanvas(25)
+ c.box(0, 0, 76, 24)
+ c.center(1, 75, 1, strings.ToUpper(cfg.PaiGow.Label()), crapsGold)
+ c.center(1, 75, 2, "FIVE HIGH / TWO LOW | COPIES LOSE | LIMITS "+betLimitsText(cfg.MinBet, cfg.MaxBet), crapsInk)
+ note := "MAIN WIN 0.95:1 | 5% COMMISSION"
+ switch cfg.PaiGow.Normalize().Variant {
+ case casino.PaiGowEZ:
+ note = "MAIN WIN 1:1 | DEALER QUEEN-HIGH PUSHES"
+ case casino.PaiGowNoPush:
+ note = "MAIN WIN 1:1 | SPLIT RESULT: EXTRA CARD HIGH WINS, TIES LOSE"
+ case casino.PaiGowFaceUp:
+ note = "DEALER FACE UP | ACE-HIGH PUSHES | PUSH ACE HIGH " + strings.ReplaceAll(cfg.PaiGow.PushAceHigh, "_", "/") + ":1"
+ }
+ c.center(1, 75, 3, note, crapsInk)
+ c.box(0, 4, 76, 19)
+ c.text(3, 5, "FORTUNE - BEST OF ALL SEVEN CARDS", crapsGold)
+ c.text(48, 5, "PROFIT ODDS", crapsGold)
+ envy := "ENVY OFF"
+ if cfg.PaiGow.Envy {
+ envy = "ENVY (FIXED)"
+ }
+ c.text(62, 5, envy, crapsGold)
+ for i, row := range casino.FortunePayTable(cfg.PaiGow.Envy) {
+ c.text(3, 6+i, row.Hand, crapsInk)
+ c.text(50, 6+i, fmt.Sprintf("%d:1", row.Odds), crapsInk)
+ if cfg.PaiGow.Envy {
+ c.text(65, 6+i, fmt.Sprintf("%d", row.Envy), crapsInk)
+ }
+ }
+ c.center(1, 75, 18, "Highest prize only. Winning Fortune wagers also return their stake.", crapsMuted)
+ note = "Envy disabled: quads improved from 25:1 to 30:1."
+ if cfg.PaiGow.Envy {
+ note = "Fortune 5+ earns Envy on other players, not yourself or dealer."
+ }
+ c.center(1, 75, 20, note, crapsInk)
+ c.center(1, 75, 21, "24 sets cards 2/4 LOW; 13456 sets five HIGH; house sets house way.", crapsInk)
+ c.center(1, 75, 22, "High must outrank low. Joker: ace or completes straight/flush.", crapsMuted)
+ c.center(1, 75, 23, "Bet main, optional bet <amount> fortune, then deal.", crapsMuted)
+ return c.render(mode, unicode)
+}
+func renderPaiGow(s *casino.PaiGowSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) string {
+ size = cardRenderSize(unicode, size)
+ lines := []string{renderPaiGowFelt(s.Config, mode, unicode)}
+ if len(s.Cards) > 0 {
+ lines = append(lines, "Your seven cards (positions 1-7):")
+ lines = append(lines, cardsLine(mode, s.Cards, size, unicode, style, store)...)
+ // Position labels use each card's actual rendered width; compact cards are
+ // variable-width, so print a separate unambiguous position-to-card key.
+ var positions []string
+ for i, c := range s.Cards {
+ positions = append(positions, fmt.Sprintf("%d=%s", i+1, cardSymbol(c)))
+ }
+ lines = append(lines, strings.Join(positions, " "))
+ }
+ for _, h := range []struct {
+ label string
+ cards []casino.CardView
+ }{{"Your high", s.High}, {"Your low", s.Low}, {"Dealer high", s.DealerHigh}, {"Dealer low", s.DealerLow}, {"Tiebreaker: player / dealer", s.Tiebreaker}} {
+ if len(h.cards) > 0 {
+ lines = append(lines, h.label+":")
+ lines = append(lines, cardsLine(mode, h.cards, size, unicode, style, store)...)
+ }
+ }
+ if s.Result != "" {
+ lines = append(lines, s.Result)
+ }
+ return strings.Join(lines, "\n")
+}
diff --git a/internal/game/render_roulette.go b/internal/game/render_roulette.go
index 0e4b1a3..9ffd355 100644
--- a/internal/game/render_roulette.go
+++ b/internal/game/render_roulette.go
@@ -601,6 +601,9 @@ func (g *Game) renderCasinoRules(sess *net.Session, cfg casino.TableConfig) stri
// The whole rule card is a rendered felt.
return g.renderCasinoSnapshot(sess, &casino.BlackjackRulesView{Config: cfg})
}
+ if cfg.Game == casino.GamePaiGow {
+ return cfg.RulesText() + "\n" + renderPaiGowFelt(cfg, g.colorMode(sess), sess.Player.OptionBool("unicode"))
+ }
if cfg.Game == casino.GameBaccarat {
return g.renderCasinoSnapshot(sess, &casino.BaccaratRulesView{Config: cfg})
}
diff --git a/internal/game/sys_triggers.go b/internal/game/sys_triggers.go
index 7a6466a..d1ce336 100644
--- a/internal/game/sys_triggers.go
+++ b/internal/game/sys_triggers.go
@@ -102,7 +102,7 @@ func (g *Game) SequenceTick() {
g.seqMu.Lock()
// g.sequences is a map: collect its keys and sort them so the per-tick
// order in which simultaneous sequences advance is reproducible (the
- // determinism contract in building_guide/tick_order.md).
+ // determinism contract in dev_guide/tick_order.md).
keys := make([]string, 0, len(g.sequences))
for key := range g.sequences {
keys = append(keys, key)
diff --git a/internal/game/tick_systems.go b/internal/game/tick_systems.go
index b819454..f85cdfa 100644
--- a/internal/game/tick_systems.go
+++ b/internal/game/tick_systems.go
@@ -1,7 +1,7 @@
package game
// tickSystem is one step of the canonical per-tick system sequence. Name is
-// the human-readable identifier also encoded in building_guide/tick_order.md;
+// the human-readable identifier also encoded in dev_guide/tick_order.md;
// Run invokes the underlying system. The slice tickSystemOrder is the single
// executable source of truth for the order in which per-tick systems fire.
//
@@ -9,7 +9,7 @@ package game
// (registered as engine subscription ID 1, which the engine fires before all
// dynamically-registered combat/aggro/respawn callbacks per processTick's
// registration-ID sort) depend on. Reordering this slice is a breaking change
-// to game behavior; keep building_guide/tick_order.md in sync.
+// to game behavior; keep dev_guide/tick_order.md in sync.
type tickSystem struct {
Name string
Run func(*Game)
diff --git a/internal/game/tick_systems_test.go b/internal/game/tick_systems_test.go
index 2fa67ac..82028d6 100644
--- a/internal/game/tick_systems_test.go
+++ b/internal/game/tick_systems_test.go
@@ -14,7 +14,7 @@ import (
// expectedTickSystemOrder is the canonical per-tick system sequence. It MUST
// stay in sync with tickSystemOrder in tick_systems.go and with
-// building_guide/tick_order.md. Updating one without the others is a bug.
+// dev_guide/tick_order.md. Updating one without the others is a bug.
//
// The list is intentionally spelled out here (rather than derived from
// tickSystemOrder) so the test fails loudly when someone reorders, adds, or
@@ -48,17 +48,17 @@ var expectedTickSystemOrder = []string{
// TestTickSystemOrder asserts the canonical per-tick system sequence matches
// the documented order exactly. This is the regression guard referenced in
-// building_guide/tick_order.md and in the master subscriber doc comment in
+// dev_guide/tick_order.md and in the master subscriber doc comment in
// cmd/thoi/main.go.
func TestTickSystemOrder(t *testing.T) {
got := TickSystemOrder()
if len(got) != len(expectedTickSystemOrder) {
- t.Fatalf("tick system count changed: got %d, want %d (update expectedTickSystemOrder and building_guide/tick_order.md)",
+ t.Fatalf("tick system count changed: got %d, want %d (update expectedTickSystemOrder and dev_guide/tick_order.md)",
len(got), len(expectedTickSystemOrder))
}
for i, want := range expectedTickSystemOrder {
if got[i].Name != want {
- t.Errorf("tick system %d: got %q, want %q (reordered? update building_guide/tick_order.md)",
+ t.Errorf("tick system %d: got %q, want %q (reordered? update dev_guide/tick_order.md)",
i, got[i].Name, want)
}
}
diff --git a/internal/game/wallet_test.go b/internal/game/wallet_test.go
index f20f96f..a74ccd5 100644
--- a/internal/game/wallet_test.go
+++ b/internal/game/wallet_test.go
@@ -4,7 +4,7 @@ package game
// stake in one atomic character save, a settlement credits and clears the
// stakes it returns in one save, a failed save reverts, and startup refunds
// anything left in flight. See internal/game/money.go for the design and
-// building_guide/concurrency.md for why two-phase recovery is out of scope.
+// dev_guide/concurrency.md for why two-phase recovery is out of scope.
import (
"os"