> For the complete documentation index, see [llms.txt](https://swu-forge.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://swu-forge.gitbook.io/docs/teams/team-battle-log.md).

# Team Battle Log

The **Team Battle Log** rolls up karabast.net games **scoped to the team** into archetype buckets, so a team can see at a glance which decks the group is grinding and how they're performing.

Each match carries a **set of teams** it's scoped to (`KarabastMatchTeam` rows — seeded at ingest, editable per-match; see [Recording scope](#recording-scope) below), and the rollup filters on it. This is what stops a member who belongs to multiple teams from having their games bleed into every team they're on: a game counts toward exactly the teams its match is scoped to — nothing automatic.

## Where to find it

From `/teams/[teamId]`, click the **Battle Log** button in the header. The page is visible to every team member regardless of role — read-only for VIEWERs is the same as for everyone else here.

## What you'll see

### Archetype list

Each row is a unique `(leader, base-group)` combination. Bases are normalised through `karabastBaseGroupKey()` so all 30HP-Vigilance common bases collapse into one archetype, while rare bases (e.g. *Lake Country*) keep their own row.

Above the list, the default **Leader + Base** view offers the same controls as the per-user [Battle Log tab](/docs/battle-log/battle-log/battle-log-tab.md):

* A **Date range** (from–to) that narrows the list to matches whose most recent game falls in the window. It's server-side and **composes with the selected** [**stats period**](/docs/teams/team-battle-log/team-stats-periods.md) — the date range further narrows within the active meta. Clear it with the ✕ next to the inputs.
* A **Sort by** control — **Recent** (default; most recently played first) or **Win rate** (over decisive games, wins ÷ (wins + losses)) — plus a direction toggle for descending/ascending. Decks with no decided games sort to the bottom in the default order.

These controls apply to the **Leader + Base** view only; the Leader / Base type / Pilot roll-up tables keep their own ordering and the stats-period filter.

Columns:

* **Archetype** — leader and (representative) base. A small `· Common 30HP` tag indicates a common-base rollup; otherwise the row is identified by the specific rare base.
* **Games** — total games this archetype has logged across the team.
* **W–L–D** — cumulative record. Draws appear only when there are draws to show.
* **Players** — unique pilots on the team who've recorded games with this archetype.
* **Last played** — relative time since the most recent game.

If any games couldn't be archetype-bucketed (karabast missed the leader or base in capture), a footer surfaces the count.

### Archetype detail

Click any row to drill in. The detail page is laid out as:

* **Header** — the leader/base hero plus the two headline rates side by side: **Game win rate** (over decided + drawn games) and **Match win rate**. A match (one recorded lobby) is scored by majority game result — more game wins than losses = a match win, more losses = a match loss, tied = a *split*; splits are excluded from the match-rate denominator. The header also shows a static pilot count and last-played, plus a **Decks** button.
* **Decks** (modal) — a mini repo of the team's decks for this archetype: every team-folder deck whose leader + base group matches (commons collapse by hp+color, so different common bases of the same group are the same archetype). Each row shows the leader/base thumbnails, a deck link, a "Created by …" subline, and **Copy link** + **Share image** buttons (the latter opens the deck's share-image generator). No win/loss stats — it's a deck list, not pilot stats. The detail page has a prominent **Matches / Card stats** toggle right under the header; the **header and filter bar persist** across both, so switching only swaps the body below.

**Matches** (the default) shows:

* **Win/loss over time** (left) and the **matchup radar** (right), side by side. Both are **filter-aware** (they recompute from the same filtered game set as the replay list) and **interactive**: clicking a radar axis sets the opponent-leader filter (click again to clear), and clicking a point on the win/loss chart sets the date filter to that bucket's window. The radar always renders even with a small sample. (The win/loss timeline ignores the *date* filter so it stays a full, clickable series — clicking a point is what sets the date.)
* **Replays by opponent** — per-opponent bucket accordions, each expanding to its games (replay links). A filter bar above them narrows the list **client-side** (instant, no refetch):
  * **Opponent leader** typeahead and **Pilot** typeahead (single-select).
  * **Wins** / **Losses** checkboxes — check one or both to restrict by result (leaving both unchecked shows everything).
  * **Date** — macros (All / 7d / 14d / 21d / 30d) or a custom from–to range. Each accordion header's record (W–L and win %) recomputes for the active filters.

**Card stats** shows the per-card **Card performance** panel — each card's win rate by stage (in deck / played / resourced / discarded), framed against the archetype's win rate, with Heatmap / Bars / Scatter / Movers views (see [Card performance](/docs/battle-log/battle-log.md#card-performance)). Aggregated across every pilot; leader and base excluded. Because the filter bar is shared, the **same opponent / pilot / result / date filters apply here too**, and the stats re-aggregate live.

Replay links go to the existing per-game viewer (`/decks/[deckId]/battle-log/[lobbyId]/[gameId]`). A member of **any team in the match's set** can watch the replay even when the deck lives in the recorder's *personal* folder — replay access is keyed off the match's team set, the same set the rollup uses, so anything listed here is openable by members of that team. (Members of the recorder's teams that the match was *not* shared with cannot open it — that's the anti-bleed scoping.)

## What counts toward the rollup

* **Games whose match is scoped to this team** — i.e. the match has a `KarabastMatchTeam` row for this team (seeded from the recorder's [recording scope](#recording-scope) at ingest, then editable per-match). A match can be scoped to several teams at once, in which case its games count toward each of them.
* Games where `karabast` recorded a leader and a recognised base (rare card id or common rollup key).

Games with no captured leader, or a base that couldn't be classified, are counted as "hidden" and surfaced in the footer. Games whose match is scoped to no team (the recorder had no team, or a multi-team user who hasn't declared a set) don't appear in any team's rollup.

## Recording scope

The set of teams a new match starts scoped to is resolved at ingest from the set the user has declared on `/teams` (the **Recording scope** checkbox list). It's intersected with the user's current memberships, so a team they've since left silently drops out.

* **No teams** → empty set (the match saves but counts toward no team).
* **All teams unchecked** → empty set — explicit opt-out works for everyone, including single-team users.
* **One or more checked** → that set.

Joining a team auto-adds it to the user's recording scope (opt-out: they can uncheck it on `/teams`).

The set is seeded only when a match is **first created**, so re-POSTs / later Bo3 games in the same lobby don't undo edits. After the fact, the match owner can **add or remove teams** on the match's replay page ("Visible to teams") or from the personal **Battle Log** top-nav tab — both changes affect which teams can open the replay and which teams' rollups it counts toward. Changing the `/teams` set only affects **future** recordings.

You can only add a team you belong to (no sharing into teams you're not on); removing is always allowed for the match owner.

### Orphaned matches

When the recording scope excludes a team deck's own team, that match becomes **orphaned** — its `deckId` is preserved but the team deck's Battle Log doesn't show it. The recording user finds it on their personal [Battle Log](/docs/battle-log/battle-log/battle-log-tab.md) tab and can **assign** it to the team there, which adds the team to the scope set and makes it appear in the team's Battle Log and rollups.

## Dashboard: headline KPIs

The team home dashboard's top KPI strip — **Games logged** and **Team win rate** — reports the team's record **against the field**, i.e. games against non-teammates. Internal games (two teammates who played each other) are excluded so that scrimmaging within the team doesn't drag the win rate toward 50% (every internal game is one teammate's win and the other's loss).

This is broader than the archetype rollup's mirror exclusion. The rollup only drops **same-deck mirrors** (`isTeamMirror` — both teammates on one shared team deck), so per-archetype win rates still include teammates who faced off on *different* decks (a deck beating a teammate's deck is real per-deck data). The headline KPIs additionally drop those different-deck internal games, detected generically: any `gameId` recorded by **two or more team members** is internal. Computed by `computeTeamFieldRecord()` and cached per (team, period) alongside the archetype list.

Internal games aren't lost — they're surfaced as their own stat under [Teammate matchups](https://github.com/acousineau/swu-deck-visualizer/blob/main/docs/howto/teammate-matchups.md).

> **Limitation:** only *double-recorded* internal games are detectable. If just one of the two teammates recorded the game, it's a single row whose opponent is only a handle string, so it can't be told apart from a game against the field and still counts toward the headline.

## Dashboard: win/loss over time

The team home dashboard (`/teams/[teamId]`) shows a **Win/loss over time** line chart. Each line is one archetype, and its value is the **cumulative net record** (wins minus losses) over the displayed range — a rising line is a deck that's been winning for the team, a falling line one that's been losing. Buckets auto-scale to the span of the data (daily → weekly → monthly).

By default it plots the team's **top 5 archetypes** by games. A team **ADMIN/OWNER** sees an **Archetypes** picklist (the slider button in the card header) to choose exactly which archetypes are charted, up to 8. That selection is **saved on the team** (`Team.dashboardConfig`) and shared with everyone — so the whole team sees the lines their admins curated. Regular members see the chart read-only, without the picklist.

It uses the same team-scope + mirror-exclusion rules as the archetype rollup above, and shares the same cache (below).

## Dashboard: Win rate by archetype + Most played

Below the timeline, two side-by-side cards summarize the team's archetypes — both keyed by **leader + base** archetype (commons collapse to their fungible class), each row showing the leader and its base label:

* **Win rate by archetype** — archetypes with **≥ 10 decided games**, ranked by win %.
* **Most played** — every archetype ranked by games played (no minimum).

Each card shows the **top 5** and reveals **one additional page** (up to 10) with **Show more** — the second page ships in the initial payload, so the reveal needs no extra request. Anything past two pages isn't surfaced here; the Battle Log is the full list. Both lists collapse back to the first page when the [stats period](/docs/teams/team-battle-log/team-stats-periods.md) selection changes.

Every row links to that archetype's [Battle Log detail page](#archetype-detail). Once a card is **fully expanded** (no more pages to reveal), a hint appears at the bottom of that card linking to the team Battle Log itself — the place to drill into matchups and game-by-game results. Both cards read from the same cached archetype rollup as the charts above (no extra DB round-trips).

## Caching

The archetype list, each detail view, and the win/loss-over-time chart are cached in-process for 10 minutes per team. The cache is invalidated automatically:

* Whenever a match scoped to the team gains a game at ingest (busts each team in the match's set).
* Whenever a team is added to / removed from a match (busts that team).

Membership changes don't affect the rollup — it's keyed on each match's team set, not on the current member list — so there's nothing to invalidate when someone joins or leaves.

## Permissions

Same as the rest of the team feature: any team member can view; the `teamsEnabled` flag on the user is required; non-members get a 404 (404 instead of 403 to avoid leaking team existence).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://swu-forge.gitbook.io/docs/teams/team-battle-log.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
