Nothing here
is guessed.
Every line ppand writes is traceable to something it actually read. This page is the full account of what gets read, where it comes from, and how it turns into a brief or a fighter.
The short version
There is no language model in this site. A brief is assembled by rules from live data, which is why the same subject always produces the same brief — and why every number on a fighter card can point at its source.
Reading a codebase
Four reads against the public repository API, plus up to two manifest files fetched raw. Around five requests per run.
| Read | What comes out of it |
|---|---|
/repos/:owner/:repo | Summary, star count, licence, topics, homepage, default branch, last push date |
/repos/:owner/:repo/contents | Top level of the tree — folders and files, dotfiles filtered out |
/repos/:owner/:repo/readme | Opening paragraph, and bullets that survive filtering |
/repos/:owner/:repo/languages | Byte counts per language, turned into percentages |
| Raw manifests | package.json, requirements.txt, pyproject.toml, Cargo.toml, go.mod, composer.json, Gemfile |
Turning that into a stack
Dependency names are matched against a lookup table, so next becomes Next.js and drizzle-orm becomes Drizzle ORM. Docker, Makefiles and Terraform are read off filenames and reported separately as tooling rather than dependencies. Anything unrecognised is left out instead of guessed at.
Filtering the readme
Bullet lists are the noisiest part of any readme, so a bullet is dropped when it is shorter than twenty characters, fewer than four words, a bare navigation word like Tutorial or Roadmap, a URL, or a path to a documentation file. If a Features heading exists, only bullets under it are used. If nothing survives, the section is labelled as a readme description rather than passed off as requirements.
Reading a website
The live markup is fetched through a relay chain, then parsed in your own browser with DOMParser. Three relays are tried in order so one bad gateway does not end the read.
- Title, meta description and any Open Graph site name
- The
h1, then section headings in document order - Header and nav link labels, with doubled labels collapsed
- Button and call-to-action labels
- Google Font families from stylesheet links, falling back to the first declared font stack
- The
theme-colorplus the most frequently used hex colours, with near-greys discarded - Framework fingerprints: Next.js, Nuxt, WordPress, Shopify, Svelte, Astro, Webflow, Framer, Angular, Gatsby, React, and Tailwind by class density
- Counts of sections, images, forms and links
The brief that comes out asks for your own copy and your own images. It reproduces structure, type and palette — the parts that are facts about the page — and explicitly tells the agent not to reuse the wording.
Reading a game
Three reads against the open encyclopedia API. Title search is scored, because a search for Doom will otherwise land on the franchise page instead of the 1993 release.
- An exact name match scores highest, a prefix match next, and a title carrying
(video game)gets a bonus - Franchise, series, disambiguation, film and novel pages are penalised out of the running
- The plain-text article gives the summary and, when present, the gameplay section
- The infobox gives genre, developer, publisher, engine, modes, platforms and release
Where the infobox has no developer, the lead sentence is used — but only the lead sentence. Further down the article that same phrasing belongs to ports and sequels, and using it produced wrong studios during testing.
How a fighter is derived
Stats belong to the fighter, not to whatever produced it. A read maps to a seed, the seed picks the traits, and the traits set the stats over a common base of HP 120, ATK 60, DEF 40, SPD 55, CRIT 12. Each card prints its own working, and the full modifier table is in the whitepaper.
| Stat | Base | Raised by |
|---|---|---|
| HP | 120 | Hair — black most, red least |
| ATK | 60 | Frown, ginger or red hair, headphones |
| DEF | 40 | Dark or grey eyes, green hair, black cap |
| SPD | 55 | Blue or brown eyes, blonde hair, pink ribbon |
| CRIT | 12 | Smile, hazel eyes, glasses, hairpin |
| SP | — | Named after the extra worn; no extra means Bare Hands |
How a battle resolves
- The faster fighter swings first; a speed tie is broken by seed, never by chance
- Damage is attack times a swing factor, minus a share of the defender's defence, with a floor of two
- Crit chance is the CRIT stat as a percentage, landing at 1.85x
- The special move fires every fourth round at 1.35x
- Thirty rounds maximum; if both are standing it goes to whoever holds the higher share of their own health
The swing factor comes from a seeded generator, and the seed is mixed from the two fighters. That is the whole trick: battles look lively but are reproducible, so a battle link shows everyone the identical fight.
The rules are also written in Solidity, in contracts/src/Bout.sol, using integer arithmetic only — no floats anywhere, and the seed mix avoids keccak precisely so the browser can reproduce it with Math.imul. The two implementations were diffed on twenty-four randomised battles and agreed on the winner and the round count every time, knockouts and thirty-round decisions alike. That is what makes an on-chain battle trustworthy: the chain cannot disagree with what you were shown.
The collection
Fighters are not drawn procedurally. They are the ppand collection, baked straight out of the source artwork into standalone trait layers and composited in your browser.
| Layer | Variants | Examples |
|---|---|---|
| Body | fixed | every fighter shares it |
| Mouth | 5 | Straight, Smile, Frown, Talking, Talking (Happy) |
| Eyes | 5 | Grey, Brown, Hazel, Dark, Blue |
| Hair | 6 | Brown, Blonde, Black, Ginger, Red, Green |
| Extra | 5 + none | Pink Ribbon, Headphones, Black Glasses, Black Cap, Yellow Hairpin |
That is 900 distinct fighters. Which one you get is not a roll: each trait is picked from the fighter's seed using the same integer generator the battle uses, so a given subject always produces the same fighter, and the selection can be re-derived on chain from the seed alone.
The trait layers are the artwork's own pixels at its native 2048 by 2048, extracted with each variant standalone and no base garment baked underneath another. Every canvas composites those native layers at 2048, the roster grid included, and each layer is fetched only when a fighter that wears it is drawn. Checked against the artwork's own compositor, the only differences are on anti-aliased layer edges — 0.2 per cent of pixels, none of them a wrong layer, offset or order.
Where the NFT layer goes
Battles work today with no wallet and no chain. The on-chain layer is deliberately staged so nothing is spent before it earns its place.
- Arcade — reads become fighters, battles resolve in the browser, links are shareable. No cost, no wallet.
- Fighter as NFT — 900 fighters, 200,000 $PPAND burned per mint plus a protocol fee to be announced, one per wallet. Traits and stats derive from the token id, so the fighter can be rebuilt from the token alone.
- On-chain battles — the same resolution rules ported to Solidity, so the contract agrees with the browser. Gas per battle, and the seed must come from the pair rather than from block data.
- Ranked entry — 2,000 $PPAND per battle, burned, with rewards paid from a daily USDG pot split 2,000 ways, one share per win. Written and verified locally, waiting on a network and the tokens.
The arena economy
Free battles stay free and need no wallet. Ranked battles are the on-chain layer, and two different assets are involved on purpose.
| Rule | Value |
|---|---|
| Entry per ranked battle | 2,000 $PPAND |
| What happens to the entry | It is burned. The arena never holds it, never spends it and never pays it back, so entering permanently removes supply. |
| Reward asset | USDG, the stablecoin |
| Daily split | Each day's USDG pot is divided into 2,000 shares |
| Reward per win | One share, pot ÷ 2000, for each battle you won that day |
| Matchmaking | A single queue. One fighter waits, the next entrant is paired against it, and the battle settles in the same transaction. |
| Claiming | Opens once the day has closed. Unclaimed shares stay claimable; shares never won are swept into a later pot. |
| Requirement | You must own the fighter you enter |
Because the entry is burned and the reward is a different asset, the pot has to be funded in USDG rather than out of entries. That is honest by construction: the contract can only ever pay out USDG it actually holds, and it can never mint a reward. A win is worth one share whether you entered once or fifty times, and payout in a day can never exceed that day's pot because shares are capped at 2,000.
Verified on a local chain rather than asserted: a 2,000 $PPAND entry reduced the token's total supply by 2,000 and left the arena holding none of it, a 4,000 USDG pot produced a share of exactly 2 USDG, a claim before the day closed reverted with DayNotOver, the winner's claim moved exactly 2 USDG, and a second claim reverted with NothingToClaim.
Minting a fighter
| Rule | Value |
|---|---|
| Total supply | 900, one per trait combination, fixed in the contract |
| Cost to mint | 200,000 $PPAND, burned before the fighter is created |
| Protocol fee | Charged in ETH on top of the burn, and forwarded to the treasury. To be announced. |
| Limit | One fighter per wallet, permanently |
| Status | Not open. The contract refuses to open the mint until the fee is set. |
The burn happens first, so a failed mint cannot destroy tokens without producing a fighter, and a successful mint has always destroyed them. The one-per-wallet limit is permanent: sending your fighter elsewhere does not restore the right to mint another.
Also verified locally: minting while closed reverts with MintClosed, a mint burned exactly 200,000 $PPAND and reduced the token's total supply by the same amount, a second mint from the same wallet reverted with AlreadyMinted, and the supply counter stops at 900.
How it launches
Both launches are stealth launches: no presale, no allowlist, no private allocation, no reserved supply and no advance notice of timing.
| Stage | What happens |
|---|---|
| 1 — $PPAND | Launches first. Minting burns $PPAND, so the token must exist before any mint is possible. |
| 2 — Fighter mint | Opens only once the ETH protocol fee is set in the contract. 900 supply, 200,000 $PPAND burned per mint, one per wallet. |
| 3 — Ranked arena | Opens once fighters exist and the USDG reward pot is funded. |
No insider gets a cheaper entry, and by the same token there is no queue and no guaranteed allocation. The whitepaper sets this out in full.
Limits and privacy
- Anonymous repository reads are capped at sixty per hour by the host. A token pasted under Manual control raises that, is stored only in your browser, and is never sent anywhere but the API it belongs to.
- Website reads depend on a public relay. Some sites refuse to be read; the interface says so rather than inventing a result.
- No account, no cookie, no analytics, no server of ours in the path. Nothing you type is stored — refresh and it is gone.