From c84a9944356f590d90d819d652bdf1349ebacd40 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 5 Oct 2026 06:03:49 -0400 Subject: feat: prediction exchange with live polymarket data --- internal/game/prediction_ux_regression_test.go | 171 +++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 internal/game/prediction_ux_regression_test.go (limited to 'internal/game/prediction_ux_regression_test.go') diff --git a/internal/game/prediction_ux_regression_test.go b/internal/game/prediction_ux_regression_test.go new file mode 100644 index 0000000..159a492 --- /dev/null +++ b/internal/game/prediction_ux_regression_test.go @@ -0,0 +1,171 @@ +package game + +import ( + "fmt" + "strings" + "testing" + + "thehouseoficarus/internal/color" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/prediction" + "thehouseoficarus/internal/world" +) + +func TestPredictionOneComplementaryBarPerMarket(t *testing.T) { + for _, unicode := range []bool{true, false} { + t.Run(fmt.Sprint(unicode), func(t *testing.T) { + g, s, conn, m := predictionFixture(t) + s.Player.Options["unicode"] = unicode + for _, detail := range []bool{false, true} { + conn.data = nil + if detail { + g.predictionView(s, m, nil) + } else { + g.predictionList(s, []prediction.Market{m}, 1, "") + } + output := string(conn.data) + bars := 0 + for _, line := range strings.Split(output, "\n") { + if strings.Contains(line, "█") || strings.Contains(line, "#") && strings.Contains(line, ".") { + bars++ + } + } + if bars != 1 { + t.Fatalf("detail=%v: got %d bars, want one\n%s", detail, bars, output) + } + if !strings.Contains(output, "YES") || !strings.Contains(output, "42.1%") || !strings.Contains(output, "57.9%") || !strings.Contains(output, "NO") { + t.Fatal("missing two-sided odds labels") + } + if strings.Contains(output, m.Description) || strings.Contains(output, "RESOLUTION RULES") { + t.Fatal("resolution text shown by default") + } + if detail { + for _, quote := range []string{"CHOOSE YOUR OUTCOME", "Outcome", "Buy", "Sell", "43", "42", "58", "57"} { + if !strings.Contains(output, quote) { + t.Fatalf("missing integer quote %q", quote) + } + } + } + } + }) + } +} + +func TestPredictionRulesFramePreservesTextWithoutCarriageReturns(t *testing.T) { + for _, width := range []int{40, 80} { + t.Run(fmt.Sprint(width), func(t *testing.T) { + g, s, conn, m := predictionFixture(t) + s.Player.Options["wrap_width"] = width + url := "https://example.org/" + strings.Repeat("long-resolution-source", 10) + m.Description = strings.Repeat("This market resolves according to the party controlling the chamber. ", 12) + "\r\n\r\n" + url + predictionTestServer(t, g, m) + g.doUse(s, "market") + g.handleGameCommand(s, "1") + if strings.Contains(string(conn.data), "RESOLUTION RULES") { + t.Fatal("rules appeared before request") + } + conn.data = nil + g.handleGameCommand(s, "rules") + text := strings.ReplaceAll(string(conn.data), "\r\n", "\n") + if strings.Contains(text, "\r") { + t.Fatal("bare CR inside frame can overwrite text") + } + var body strings.Builder + for _, line := range strings.Split(text, "\n") { + if strings.HasPrefix(line, "║") { + if !strings.HasSuffix(line, "║") { + t.Fatalf("missing right edge: %q", line) + } + if color.VisibleLen(line) != width { + t.Fatalf("row width %d, want %d: %q", color.VisibleLen(line), width, line) + } + body.WriteString(strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "║"), "║"))) + } + } + if !strings.Contains(text, "RESOLUTION RULES") || !strings.Contains(body.String(), url) { + t.Fatal("rules or unbroken URL was lost during wrapping") + } + if s.PredictionScreen != "view" || s.PredictionSelected != m.ID { + t.Fatal("reading rules lost selected market") + } + }) + } +} + +func TestPredictionLookUsesCasinoMenuAndRoomFirstOrder(t *testing.T) { + g, s, conn, m := predictionFixture(t) + g.Hub = net.NewHub() + g.MobStore = world.NewMobStore(t.TempDir()) + s.Player.Options["tiny_map"] = "off" + s.Player.Options["casino_hints"] = "once" + predictionTestServer(t, g, m) + g.doUse(s, "counter") + g.handleGameCommand(s, "1") + conn.data = nil + g.handleGameCommand(s, "look") + text := strings.ReplaceAll(string(conn.data), "\r\n", "\n") + room, screen := strings.Index(text, "Test Counter"), strings.Index(text, "THE MARKET") + if room < 0 || screen < room { + t.Fatalf("look not room first: %s", text) + } + expected := g.renderCasinoCommands(s, "Available 10000 credits", "", g.predictionCommands(s)) + if !strings.Contains(text, expected) { + t.Fatalf("look did not use the shared casino menu\nwant: %s\ngot: %s", expected, text) + } + for _, unwanted := range []string{"YOUR NEXT MOVE", "what -", "what /", "RESOLUTION RULES"} { + if strings.Contains(text, unwanted) { + t.Fatalf("look leaked %q", unwanted) + } + } + conn.data = nil + g.handleGameCommand(s, "what") + text = strings.ReplaceAll(string(conn.data), "\r\n", "\n") + if !strings.Contains(text, g.casinoMenuCommands(s, g.predictionCommands(s))) { + t.Fatal("normal what did not use casino hint renderer") + } + if g.predictionInputOwned(s, "look") || g.predictionInputOwned(s, "what") { + t.Fatal("counter hijacked normal world command") + } + conn.data = nil + s.Player.Options["casino_hints"] = "off" + g.handleGameCommand(s, "look") + if strings.Contains(string(conn.data), "view N") { + t.Fatal("look ignored casino_hints off") + } +} + +func TestPredictionFrameLongColoredToken(t *testing.T) { + g, s, _, _ := predictionFixture(t) + s.Player.Options["color"] = "xterm256" + token := g.colorize(s, "casino_action", strings.Repeat("x", 160)) + lines := predictionFrameLines(token, 36) + for _, line := range lines { + if strings.Contains(line, "\r") || color.VisibleLen(line) > 36 { + t.Fatalf("unsafe wrapped row %q", line) + } + } +} + +func TestPredictionOddsBarsAlign(t *testing.T) { + g, s, _, _ := predictionFixture(t) + s.Player.Options["color"] = "none" + target := 36 + pairs := [][2]string{{"Yes", "No"}, {"M80", "TYLOO"}, {"9z", "Natus Vincere"}, {"Falcons", "Saints"}} + cells := make([]string, 0, len(pairs)) + for _, pair := range pairs { + m := prediction.Market{ID: "1", Outcomes: `["` + pair[0] + `","` + pair[1] + `"]`, Tokens: `["a","b"]`, Prices: `["0.421","0.579"]`} + cells = append(cells, g.predictionOddsCell(s, m, target)) + } + start := strings.Index(cells[0], "█") + if start < 0 { + t.Fatalf("no bar: %q", cells[0]) + } + for _, cell := range cells { + if got := strings.Index(cell, "█"); got != start { + t.Fatalf("bar offset %d, want %d: %q", got, start, cell) + } + if color.VisibleLen(cell) != target { + t.Fatalf("cell width %d, want %d: %q", color.VisibleLen(cell), target, cell) + } + } +} -- cgit v1.2.3