A dev note on how the JWorld team generates and draws all 180 album fish in Bobber Rise from a handful of code parts, with the real numbers, the distinctness rules and the tests that guard them.
Bobber Rise has an album of 180 fish. Every one has its own look, its own name, a size range and a pull pattern for the reel fight. Drawing 180 fish by hand would have taken a long time, and the game ships with a bundle target of 2.5 MB or less. So the JWorld team did something different: there are no fish image files at all. Every fish is drawn by code from a short list of parts.
This note walks through how it works, using the real source of the game. It covers the parts, the generator, the rule that keeps two fish from looking alike, and how drawing stays cheap.

The parts box
A fish is not a picture. It is a small record of choices, and the drawing code reads that record. These are the parts the drawer knows:
| Part | Variants | What it changes |
|---|---|---|
| Body | 12 | Silhouette: slim, round, deep, flat, torpedo, eel, boxy, spiky-back, big-head, long-snout, crescent, plump |
| Tail | 5 | Fan, fork, crescent, ribbon, round |
| Fins | 4 sets | Small, sail, long, double |
| Pattern | 8 | Plain, stripes, spots, bands, scales, belly gradient, saddle, speckle |
| Palette | 16 | Three colors each: body, pattern and accent |
| Accessory | 10 | Only on legends: crown, whiskers, glow, shell, horns, ribbon, lantern, gold star, halo and more |
A body is not a stored image either. It is a formula. Each of the 12 bodies is a row of seven numbers (depth, how far along the peak sits, roundness, top and bottom multipliers, tail-end width and length scale). The code samples the outline at 28 points along the fish, walks along the top, back along the bottom, and closes the path. Changing one number turns a slim fish into a plump one. The same trick, on a smaller scale, draws the tails and fins.
On top of the shape come a few fixed touches that make all fish feel like one family: a single 2 px dark outline, one white highlight stripe on the back, and a big round eye. Because they are shared, even the strangest part combination still looks like a Bobber Rise fish.
The generator: a seed in, 180 recipes out
The recipes are not rolled while you play. A build-time script runs a seeded generator (the seed is the text fishing-v1) and writes species.json. The game only reads that file. That has two good effects. Every player sees the same album, and a fish never changes its look between updates.
Each species record holds its pond, tier, the part choices, a size range, name parts, a preferred time of day and zone, and its pull pattern. The count per pond and tier is fixed by design:
| Tier (in game) | Count |
|---|---|
| Common | 61 |
| Uncommon | 52 |
| Rare | 37 |
| Epic | 20 |
| Legend | 10 |
That makes 180 fish, 30 for each of the 6 ponds. Pond 1 has 14 commons, 10 uncommons and 6 rares and nothing above. Pond 6 has 6 commons, 7 uncommons, 6 rares, 6 epics and 5 legends. So the rarest fish live in the last waters.
The generator fills the most constrained tiers first (legends, then epics, rare, and so on). For each new fish it tries random part combinations, throws out any that break a rule, and keeps the best of up to 48 valid candidates. "Best" means two things. The fish should differ from its neighbours in the same pond in all three of body, pattern and hue. And it should use parts that are still under-used, so no body or color ends up everywhere. If a layout cannot be completed, the generator starts over with the next attempt number, up to 50 times.
What "different" means
The hard part of 180 fish is that random fish look alike. The generator enforces three rules.
| Rule | Where it applies |
|---|---|
| Any two fish differ in at least 2 of 3: body, pattern, hue family | Inside one pond (one album page of 30) |
| Any two fish differ in at least 2 of 4: body, pattern, hue family, tail | Across all 180 |
| No two fish share the same body + pattern + palette | Across all 180 |
The hue family is a group of 8 (pink-red, orange, amber, yellow, green, teal, blue, violet), with two of the 16 palettes in each. That keeps "two different greens" from counting as different fish.
We wanted the strongest rule, "2 of 3 for every pair among 180", at first. It turned out to be impossible. A code made of 12 bodies, 8 patterns and 8 hue families can hold at most 8 × 8 = 64 entries where every pair differs in at least two places. So the design document was changed: strict inside a pond, a looser four-part rule across the whole album. The rule is also honest about its limit. Two fish from different ponds can share a body and a pattern if their hue and tail differ.
There are tier rules on top, so rarity can be read at a glance:
- Commons use only plain, stripes or spots.
- Rares, epics and legends are never plain.
- Epics always get the sail or long fin set.
- Each of the 10 legends carries its own accessory. No two share one.
Names come from the same trick
Names are also built from parts: a prefix (24 options) and a noun (12 options, one per body). Prefixes are colors, skin patterns and places. English gives names like Jade Flatkin or Rosy Slimkin; the Korean names end in "-몽", such as 풀빛 통통몽. The generator picks the prefix that matches the fish's color or pattern first, and falls back to a place or mood word only if the pair is already taken. The noun matches the body, so a fish named "…Ribbonkin" is always the eel-shaped one. The names are fictional "pocket fish" and make no scientific claims.

Drawing: one function, any size
Every screen draws fish through a single function, drawFish. It takes the recipe, a position and a width in pixels, and fits the whole fish, tail included, into a box of that width and 0.75 of it in height. So a tiny album cell and the big catch card use the same code, and every species reads at the same size on screen. The size in centimeters changes only the numbers, not the picture.
A few details keep it light and clear:
- Bounds are measured once. A recording stand-in for the canvas runs the drawing without painting and notes the extent of every point. The result is cached per body, tail, fins and accessory combination.
- Small sizes get a simpler skin. Under 56 px, fine patterns like scales and speckle become a flat tint, because they would just turn to noise.
- Dots are seeded by the species id. The spots on a spotted fish are placed by a tiny seeded generator keyed on the id, so the same fish has the same spots every time you open the album.
- Locked cells are the same drawing. An undiscovered fish is drawn in silhouette mode: all fills and outlines at a faint dark alpha and no details. You can see its shape and guess what is waiting in the pond.
- Extras are draw calls. A glow ring for legends, a rim for night water and a brighten effect for the burst warning are options on the same function.
Tests that keep 180 honest
Because the whole album is data, it can be checked by tests. The species tests in the repo do things like this:
- Regenerate everything from the seed and compare it with the saved
species.json. If the generator or the file changes alone, the test fails. - Check there are exactly 180 ids, and that each pond has the right count per tier.
- Compare every pair of fish, which is 16,110 pairs, against the distinctness rules.
- Check each tier rule, and that only legends carry an accessory, each a different one.
- Check every body, tail, fin set, pattern and palette is used at least once, and every size range stays inside its tier.
A pure-logic check also makes sure the core code never calls random or clock functions, so the same seed always gives the same result.
What this means for players
Behind the scenes it is a lot of arithmetic. On screen it is a simple promise: every cell in the album is a different fish, and rarer ones look more special. If you are filling the album, here is how to read it:
- The silhouette of a locked cell is its real shape. Use it to guess which pond it belongs to.
- Legends are the only fish with an accessory. A crown, a halo or a lantern means you have found one.
- Each fish can earn up to three stars for size, so one catch is not always the end of that cell.
You can try the album yourself in Bobber Rise. For the reel fight and gear order, see our earlier guide to reeling.
Summary
- Every fish is drawn by code from parts: 12 bodies, 5 tails, 4 fin sets, 8 patterns, 16 palettes and 10 legend accessories.
- A seeded generator (
fishing-v1) writes the 180 recipes at build time; nothing about a fish is random at play time. - Rules force fish to differ: 2 of 3 traits inside a pond, 2 of 4 across the album, and no repeated body + pattern + palette.
- One strict rule for all 180 pairs is mathematically impossible with these parts (a 64-entry cap), so the final rule is split in two.
- A single draw function handles every size, simplifies fine patterns on small cells and draws locked fish as silhouettes.
- Tests check all 16,110 pairs, so a new fish can never quietly become a twin.
Written by the JWorld team from our game design documents and measured play data, with AI tools helping with the writing.