Skip to content

Latest commit

 

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PacketBrawl

A turn based tactical battler for the browser

Four characters a side. One player each. Speed decides who moves.

CI TypeScript Node 24 pnpm All rights reserved


What this is

PacketBrawl is a 1v1 tactical battler set in the Soultale world. Each player brings a squad of four characters. Characters act one at a time, in an order that speed decides across both squads together, so a fast character can act twice before a slow one acts once. Both players see the order coming.

A match ends when one squad has nobody left standing.

What runs today

A hotseat match is playable in a browser. Two players, one keyboard, state in memory. That is M1, and it is what the rest of the build order sits on.

pnpm install && pnpm dev
  • packages/sim holds the whole game: turn order, damage, crit, abilities, resources, targeting, a state hash, replay, and an event stream.
  • apps/web draws it. Four characters a side, a turn forecast rail that is exact rather than estimated, and a rolling combat log.
  • 187 tests pass, and no browser appears in any of them.

The characters are real. packages/content holds eight from the published Soultale chapters, each citing where it came from. The pack is hashed into a version every match records, so a balance change cannot make an old replay resolve under new numbers in silence.

Nothing in it references plot. A soul, a fighting style and a name are things a reader already has; an event is not, and the game is read by people who are behind on the story. The portrait slots are drawn and still empty, waiting on art rather than on a decision.

There is still no network, no account, and no second machine.

The one architectural rule

packages/sim is the whole of the game's logic, and it depends on nothing.

No React, no DOM, no Next.js, no Supabase. No clock, and no random source that a state does not carry. The client imports it to draw a match and to show a move at once. The server imports the same module to decide what actually happened. One implementation of the rules, never two.

The rule is structural rather than advisory:

  • The sources are a TypeScript project with no ambient types, so process, Buffer, and document do not compile there.
  • A test reads every source file and refuses any import that is not a file beside it. A list of forbidden packages would miss the package nobody thought of. Allowing only a sibling file misses none.
  • The same test refuses Math.random, Date.now, and new Date.

If a change to the rules needs a React file touched, the boundary is broken. Fix the boundary.

Quick start

Node 24 and pnpm are needed. The Node version is in .nvmrc.

pnpm install

Run everything the way CI runs it:

pnpm verify

That is lint, type check, tests, then the build. The parts run alone too:

pnpm test
pnpm typecheck
pnpm format

Run the game itself:

pnpm dev

How the turn order works

Every character walks the same distance to reach a turn. Speed sets how fast they walk, so a character with twice the speed arrives twice as often. Nothing takes a turn in rotation, and nothing is owed a turn because the other side just had one.

A character of speed 250 reaches turns at 40, 80, and 120. A character of speed 100 reaches one at 100. So the fast character acts twice, and only then does the slow one act at all.

The distance is a whole number rather than 10000 divided by speed. A fraction would be hashed and compared on two machines, and two machines do not have to agree about the last bit of one.

When two characters arrive together, the side that has waited longer goes first. Without that rule one squad takes every tie for a whole match.

Nothing acts outside this order. There is no ability that interrupts, so the list of upcoming turns is a promise rather than an estimate, and a test holds it to what the match goes on to do.

Project structure

PacketBrawl/
├── apps/
│   └── web/              the interface. imports the sim.
│       ├── src/lib/      what the screen shows, as pure functions
│       ├── src/components/
│       └── test/
├── packages/
│   ├── sim/              the rules. depends on nothing.
│   │   ├── src/
│   │   └── test/
│   └── content/          the characters. hashed into a version.
├── docs/
│   ├── architecture.md   how the pieces fit, and why
│   ├── combat.md         how a hit resolves, and why
│   ├── deployment.md     where it runs, and the settings not in git
│   └── milestones.md     the build order, and what changed from the spec
├── scripts/
│   └── check-links.sh    every relative link in every document
└── .github/workflows/    lint, type check, test, CodeQL

packages/bot does not exist yet. It arrives at the milestone that needs it, rather than standing empty until then.

Testing

pnpm test

The rules engine needs no browser, which is the whole reason it is kept apart from anything that draws it.

Two kinds of test are worth naming:

  • Recorded values. The generator's output and a state hash are pinned to the values this implementation produces. A stored match replays through both, so changing either invalidates every match recorded before the change. That has to be a decision somebody makes, not a side effect of a refactor.
  • The forecast against reality. The order shown to players is checked against the order the match actually takes, over several speed pairings.

Contributing

Read CONTRIBUTING.md first, and its licence section before anything else, because this project is not open source and that changes what contributing means.

AGENTS.md is the full set of rules: how commits are split, how text in this repository is written, and who signs a commit.

For help, see SUPPORT.md. To report a security problem privately, see SECURITY.md.

License

PacketBrawl is not open source. Copyright (c) 2025-2026 Stiven Gjekaj, all rights reserved. See LICENSE and TERMS.md.

You may read this source and fork it inside GitHub, because GitHub's Terms of Service give every GitHub user that right. You may not use, copy, modify, or distribute it without written permission. Reading the source grants you no licence to it.

About

A Soultale squad battler. Four characters a side, one player each, and if you're fast enough you act twice before they act once. Not playable yet.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Sponsor this project

Used by

Contributors

Languages