Skip to content
← All work

Case study · Ice Age Life

One whole life,before history.

A life sim of the last Ice Age that I designed, built and shipped alone. Be born into a band, live one life to its end, then start over as someone new.

The homeland card: Dnieper Mammoth Steppe, 22,000 years ago. Begin Unoru's life.
The run hub: Unoru at 18, health, spirit and prestige, and a life card about stories around the fire.
The season board: Mudslide, Wolves on the Trail and First Greens, with the band waiting in the hand.
Scroll

Every life in it ends. That is the game.

The hard part was a world deep enough to surprise me that a stranger could still get through their first spring.

Role

1person,
every hat
  • Game design
  • Interface
  • Art direction
  • Code

Timeline

6,678 commits since March 23, and 12 native releases since June

1.01.31.61.7.3First commitApp StoreWeb

Platforms

  • iPhone & iPadApp Store
  • AndroidGoogle Play
  • Webiceagelife.cc

One Expo and React Native codebase, TypeScript end to end.

Scale

icons, one style
1,175
faces, birth to old age
1,760
days with their own theme
366
tests in the suite
13,000+

The short version

The game

You live one prehistoric life, birth to death, a year at a time. When it ends, you start again as someone new.

The hard part

A simulation deep enough to surprise its maker, that a new player can still get through their first spring.

The craft

A season board you can read at a glance, a world that keeps itself alive, and art and words held to strict rules.

Honesty

The audience is small: 22 devices reached their first season in October. So this is design reasoning, simulations and what that small group showed me, not a growth chart.

One whole life

Born, grown, gone. Then someone new.

You’re born into a band of Ice Age foragers, 22,000 years ago, and live one life a year at a time: childhood, a name at 18, a hearth, children, old age. When you die, the run is over, and your soul starts again as someone else.

Inside a hide-covered mammoth-bone house, a grandmother holds a newborn up to the firelight while the mother rests.
Every run starts here: born into a band, with a family you didn't choose.
18years

Initiated

When the bands meet, you're offered names, shaped by what you've done.

Drag through a life. Every person in the game has a face for each stage, and grows into it.

A year a turn

Each year brings a life card to answer and a season board to play.

Death is the mechanic

There's no save to reload. The life is over, and the next one starts as someone new.

Everyone else is living too

Thirty-odd people, each with a life of their own.

Nobody in the band is scenery. They pair up, have children, grieve, fall out and move away on their own schedules, and each of them has a mood and a feeling about you.

The band, sorted into the day's parties: gatherers by the creek, small-game hunters, a hunting party, and those at camp.
Dahak's profile: her mood, your bearing toward her, and what you can do about either.
Morning in a steppe camp: a gathering party heads for the creek while hunters check their spears and children chase a puppy.
Thirty-odd people, each living a life of their own.

Up the ladder, and no further

A band can grow into something bigger: proto-tribe, big man, paramount, chief. The game stops at early chiefdom on purpose. Past that, you're no longer living one person's life.

Eight homelands

The Ice Age is live across eight homelands, from the Mammoth Steppe to a glacier coast at the world's edge. Later eras wait behind a lock.

  • Open grass steppe under a huge sky, a mammoth family crossing a river, a small hide camp on the bank.

    Dnieper Mammoth Steppe

  • The Ordos loess plateau: yellow loess terraces cut by deep gullies, dry grassland, wild horses running ahead of a storm front.

    The East Wind Plains

  • The red desert heart: red sand, spinifex, a dry riverbed and a lone rock rising at dusk.

    The Great South Land

  • The Altai: snow-capped peaks above a frozen river valley edged with larch, a small line of travelers on the ice.

    The Frozen Heart

  • The canyon country of southern Namibia: a deep dry river canyon, a rock shelter with ochre animal paintings, springbok on the rim.

    The Painted South

  • The Dordogne: a green river under pale limestone cliffs with rock shelters, reindeer crossing at a ford, morning mist.

    The Sheltered Valley

  • The Central Rift highlands: an escarpment above acacia savanna and a lake, zebra and giraffe, a band resting under one wide tree.

    The Great Rift

  • The Bering coast: a blue glacier meeting a cold sea, seals on the floes, a skin boat pulled up on a shingle beach.

    The World's Edge

The season board

The year arrives as a board.

Each year opens on what the season brought: threats like wolves or a mudslide, bounties like the first greens. You decide who meets which. Send the wrong person at a wolf pack and you lose them. Send nobody and everyone goes hungry. Try a round.

Spring, The Green ReturningYear 0 · Round 1
0 food2 at risk
ThreatMudslideNavigate 46 · +4 foodNobody
ThreatWolves on the TrailHunt 50 · +12 foodNobody
BountyFirst GreensForage 36 · +6 foodUntouched
Drag a person onto a lane, or tap them and then a lane. The badge on each front tells you before you commit.

The badge tells the truth

Each front says whether it holds before you commit, and counts harm that reaches past its lane, like a threat that strikes camp.

Show the sum

Every result prints its arithmetic, like Hunt 53 +21 lent = 74 vs 50. A loss you can check is one you learn from.

Spring melt on a steppe trail: two hunters with spears face three wolves while a mudslide breaks loose on the slope behind them.
Wolves on the Trail, Hunt 50. Who do you send?

Where new players quit

Twenty-two reached their first spring. Four got past it.

Play data showed newcomers quitting inside the very first board. The birth year ran about eight rounds because the whole band had to play, Auto-play stayed grayed out all year, and the board could dead-end on “Fill 1 more column” with nobody left in your hand.

22 devices reached their first season

4 got past it

October 2026, from the game’s own analytics. A small number, and enough to see a wall.
A spring board with an empty hand and the button reading Fill 1 more column: the dead end.

The dead end: an empty hand, and a button asking for one more column.

Birth yearBeforeAfter
Who places the bandyou, everyone, by handyou for two rounds, then the band
Rounds in the birth yearabout 82
Auto-playgrayed out all yearon the main button from round 1
A lane nobody can worka dead endsettles itself

Two weeks, four changes

  1. Two thirds of first lives were ending before age 5. A learning life now shelters you: until you've lived to 5 once, the board never seats your own baby, and a death on a lane becomes a wound you pull through.

  2. Auto-play from round 2, a button that moves someone into the empty column, and an analytics event for every round, so the next look would have numbers.

  3. The numbers came back: 4 of 22 got through. Nobody tapped Auto-play before round 8, though it had been there since round 2.

  4. The redesign, out the same evening. A first life's birth year is two rounds, the first by hand and the second with Auto-play on the main button. Then the band finishes the season on its own.

A band that stays alive

The bands were dying out. The sims said so.

A life sim needs a band that’s still there when you’re old. Measured against real forager life tables, mine wasn’t: run 20 bands for 60 years in the loop the game actually plays, and every one fell below ten people. Step through two days of fixes.

The same double count, for adults

The yearly roll was a full forager table with the board's dangers on top. It now keeps only what the board doesn't cover.

Bands that fell below 10 people by year 60

5%

Children in the band who died before 15

47.3%
Shaded: foragers 40-50%

Adults 15-44 who died each year

2.3% a year
Shaded: foragers 1-1.5%
Two days of commits, one number each. Step back to the start to see where it began.
A family with packs and a dog walks away from camp toward a neighboring band's smoke on the horizon, friends waving goodbye.
When a band grows too big for its homeland, a whole household leaves to join the neighbors.

A household leaves, not a stranger

Past about 35 people on the Mammoth Steppe, a band sends the household least tied to it to a neighboring band. Never yours, a leader's, or a pregnant one.

Two seeds is not evidence

Runs vary hugely from seed to seed, so every change is judged on 20 to 40 seeds over 60 to 100 years, against a recorded baseline.

1,175 icons, one hand

A word game you can read at a glance.

The store page says it’s all words, and it is. But every tile, front, person and choice also carries a drawing, so a crowded screen has a shape before you read it. Filter the set.

Hover or tab through to read a name.1,175 icons in the game; these are 172 of them.
  1. Drawnby hand or generated, to a written style guide
  2. Tracedinto one path on a 256 square
  3. Taken innamed and filed by an intake script
  4. Builtinto components
  5. Registeredin the icon map, by hand
  6. Reviewedand redrawn if it reads as an abstract shape

11 looks × 16 temperaments × 2 × 5 ages = 1,760 faces

Every choice means something

Each choice on a card gets its own icon for what it does: none shared on a card, no gray placeholder. A lone Continue is a walking figure. A test fails if one is missing.

No emoji, anywhere

Emoji draw differently on every phone and break the hand-made look. The game only speaks in its own set.

Words that state the rule

Teaching copy says the rule.

Ice Age Life is mostly words, so the words get rules too. Flavor keeps its voice. A sentence that teaches you something drops the mood and says it plainly.

Before

While you’re still small, you place the band by hand - that is how the seasons teach you. Auto-fill opens once you’re old enough to help set them.

After

Auto-fill unlocks at age 4. Until then you place the band by hand.

  • “The thaw’s here at last. The hungry season is broken.”
  • “The ice age still grips the world, and the wolf is a rival at the kill.”
  • “Dahak is simply herself today: present, capable, unhurried.”
  • “Then winter takes you too. It takes everyone.”

One house voice

Modern, plain and casual, like a person talking, not a narrator. Words that sound like a costume get cut.

The store page has to be true

The listing still said “Free, and no ads” after a version with ads had shipped. I found it in July and rewrote it.

A color for every day

The app wakes up a different color every day.

Each of the 366 days has its own pair of palettes, one for light mode and one for dark. Seasons, holidays and animals each get a day: Halloween is a pumpkin patch by day and All Hallows’ Eve by night.

JanFebMarAprMayJunJulAugSepOctNovDec
January 1Light outside, dark inside

Light · newyeardawn

Dark · firstdawn

366 days, two themes each, drawn from 707 palettes. The app opens in a different color every day of the year.

One codebase, three stores

iPhone, Android and the web, from one codebase.

It’s one Expo and React Native app in a TypeScript monorepo with shared types. The web version is the same game, exported and served at iceagelife.cc, and one capabilities file decides what each platform gets: on the web there are no in-game ads, purchases or accounts, just a banner under the game.

Choose your fate: three lives to be born into, each with a face, a homeland and a clan.
The Sun Rock Band: food, morale, the day's parties and who is in each.
The local map, where what you camp beside decides what you eat.

Offline first

A run never needs a server. The save lives on the device; an optional account only backs it up, one snapshot, never merged.

No login wall

The app opens straight into play. Backup is offered after your first finished life, not before your first tap.

A deploy that checks itself

The web deploy loads the live site afterwards and fails if the game isn't being served in its frame.

Fixes without a release, if you get the order right

Over-the-air updates are stamped with the app’s version at the moment they’re published, and only builds of that exact version take them. Try both orders.

app.json at publish time1.7.3pick an order
  • iPhonebuild 1.7.3
  • Android phonebuild 1.7.3
  • iPadbuild 1.7.3
  • iPhone, not updatedbuild 1.7.2
Same code, same fix. Only the order differs.

Tests that can fail

A test that builds its own world only proves that world.

About 13,000 tests run on the game. The rules I trust most came from green suites that shipped bugs: ten homeland cards passed every test and never fired once, because the tests built their runs by hand instead of the way the game does.

Pin randomness, never assign it

jest
  .spyOn(Math, "random")
  .mockReturnValue(0.99);

// never: Math.random = () => 0.99;

A raw assignment outlives its test and pushes later suites down paths nobody chose. A test fails the suite if anyone writes one.

Seed each system on its own

const rng = createRng(
  deriveSeed(runId + "dogs", turn, 0),
);

A new system draws from its own stream, so switching it on doesn't reshuffle every other system's dice.

Fail first

A regression test is unproven until it fails against the original bug, with a control case beside it.

Sweep both ways

Face file names once disagreed with 80 shipped files on the order of two words, and a whole look rendered as a stand-in. Now every name has a file, and every file a name.

5,000 boards

A seeded sweep plays 5,000 boards and checks that every stuck hand has a move that clears it.

What I learned

Build the systems, then watch them.

By a stone oil lamp in a cave, a girl presses her right hand to the wall while an older woman blows ochre around it, beside the faded handprints of people long gone.
Then winter takes you, and your soul starts over as someone new.
  1. Systems, then measurement.Most of a life isn’t scripted; it comes from the band, the land and the season meeting. That only works if I can measure the systems, which is why the sims exist.
  2. The first minute decides.The deepest simulation didn’t matter while new players quit in their first spring. Twenty-two devices were enough to find the wall.
  3. Make the wrong thing loud.Tests that can fail, a deploy that checks the live site, a store page that has to be true.