Back to Squad Table
SOURCE / PINNED RELEASE

Made of little things.

Squad Table

Release
d36201f8ed4f…
Author-recorded commit
7b9bb681d6ba…
License
LICENSE
Author’s source reference
nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-c043b0c0f1d

Archive hash verified: 8c0604b31f00cc5a…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/catalog.ts
// Counter catalogue. Artwork is drawn by the app; stat lines are editable on any
// counter because players bring their own rulebooks and unit tables.

export type NationId = 'ge' | 'us' | 'ru' | 'mk';
export type CounterType =
  | 'squad'
  | 'half'
  | 'leader'
  | 'crew'
  | 'weapon'
  | 'gun'
  | 'vehicle'
  | 'marker'
  | 'fort';

export type Template = {
  k: string;
  nat: NationId;
  type: CounterType;
  label: string; // main stat line or name
  tag?: string; // small top caption
  back?: string; // reverse-side caption; defaults to "BROKEN"/"DM" style
  tone?: 'fire' | 'move' | 'state' | 'terrain' | 'info';
};

export type Nation = { id: NationId; name: string; short: string };

export const NATIONS: Record<NationId, Nation> = {
  ge: { id: 'ge', name: 'German', short: 'GE' },
  us: { id: 'us', name: 'American', short: 'US' },
  ru: { id: 'ru', name: 'Russian', short: 'RU' },
  mk: { id: 'mk', name: 'Markers', short: 'MK' },
};

const infantry = (nat: NationId, rows: [string, CounterType, string, string?][]): Template[] =>
  rows.map(([k, type, label, tag]) => ({ k: `${nat}-${k}`, nat, type, label, tag }));

const gear = (nat: NationId): Template[] => [
  { k: `${nat}-lmg`, nat, type: 'weapon', label: 'LMG', back: 'MALF' },
  { k: `${nat}-mmg`, nat, type: 'weapon', label: 'MMG', back: 'MALF' },
  { k: `${nat}-hmg`, nat, type: 'weapon', label: 'HMG', back: 'MALF' },
  { k: `${nat}-mtr`, nat, type: 'weapon', label: 'MTR', tag: 'light' },
  { k: `${nat}-dc`, nat, type: 'weapon', label: 'DC', tag: 'charge' },
  { k: `${nat}-ft`, nat, type: 'weapon', label: 'FT', tag: 'flame' },
  { k: `${nat}-gun`, nat, type: 'gun', label: 'AT Gun' },
  { k: `${nat}-tank`, nat, type: 'vehicle', label: 'Tank' },
  { k: `${nat}-ht`, nat, type: 'vehicle', label: 'Halftrack' },
];

const leaders = (nat: NationId): Template[] =>
  ['9-2', '9-1', '8-1', '8-0', '7-0'].map((label) => ({
    k: `${nat}-ldr${label.replace('-', '')}`,
    nat,
    type: 'leader' as const,
    label,
    tag: 'leader',
    back: 'BROKEN',
  }));

export const TEMPLATES: Template[] = [
  ...infantry('ge', [
    ['548', 'squad', '5-4-8', 'assault'],
    ['467', 'squad', '4-6-7', 'rifle'],
    ['447', 'squad', '4-4-7', 'second'],
    ['hs', 'half', '2-4-7', 'half'],
    ['crew', 'crew', '2-2-8', 'crew'],
  ]),
  ...leaders('ge'),
  { k: 'ge-pf', nat: 'ge', type: 'weapon', label: 'PF', tag: 'panzerfaust' },
  ...gear('ge'),
  ...infantry('us', [
    ['747', 'squad', '7-4-7', 'elite'],
    ['666', 'squad', '6-6-6', 'rifle'],
    ['hs', 'half', '3-3-6', 'half'],
    ['crew', 'crew', '2-2-7', 'crew'],
  ]),
  ...leaders('us'),
  { k: 'us-baz', nat: 'us', type: 'weapon', label: 'BAZ', tag: 'bazooka' },
  ...gear('us'),
  ...infantry('ru', [
    ['628', 'squad', '6-2-8', 'guards'],
    ['447', 'squad', '4-4-7', 'rifle'],
    ['426', 'squad', '4-2-6', 'conscript'],
    ['hs', 'half', '2-2-7', 'half'],
    ['crew', 'crew', '2-2-7', 'crew'],
  ]),
  ...leaders('ru'),
  { k: 'ru-atr', nat: 'ru', type: 'weapon', label: 'ATR', tag: 'anti-tank' },
  ...gear('ru'),
  ...(
    [
      ['prep', 'Prep Fire', 'fire'],
      ['first', 'First Fire', 'fire'],
      ['final', 'Final Fire', 'fire'],
      ['pin', 'Pin', 'state'],
      ['cx', 'CX', 'move'],
      ['moved', 'Moved', 'move'],
      ['dm', 'DM', 'state'],
      ['conceal', '?', 'info'],
      ['melee', 'Melee', 'fire'],
      ['smoke', 'Smoke', 'terrain'],
      ['blaze', 'Blaze', 'terrain'],
      ['rubble', 'Rubble', 'terrain'],
      ['wreck', 'Wreck', 'terrain'],
      ['wire', 'Wire', 'terrain'],
      ['fox', 'Foxhole', 'terrain'],
      ['note', 'Note', 'info'],
    ] as const
  ).map(([k, label, tone]) => ({
    k: `mk-${k}`,
    nat: 'mk' as const,
    type: (tone === 'terrain' ? 'fort' : 'marker') as CounterType,
    label,
    tone,
    back: k === 'smoke' ? 'Dispersed' : k === 'first' ? 'Final Fire' : undefined,
  })),
];

export const TEMPLATE_BY_KEY = new Map(TEMPLATES.map((t) => [t.k, t]));

export function templatesFor(nat: NationId): Template[] {
  return TEMPLATES.filter((t) => t.nat === nat);
}