nissa/

Nissa

Interactive 3D history mini-game set in Nice at two turning points of its past, built to test what a model produces when the subject expertise sits on my side and the craft on its own.

Solo builder·Personal
Live

Stack

AI
Claude FableClaude Code
Frontend
Three.jsJavaScript

About

Nissa, the city’s own name in Nissart, is an interactive history mini-game set in the County of Nice, built as a series of episodes. Each episode is a playable period with its own map, its own characters and its own puzzles. Two are finished, the Franco-Ottoman siege of 1543 and the plebiscite of 15 April 1860 on becoming French, and three more are sketched out.

The game opens by double-clicking a file. There is no server, no installation and no internet connection involved. Three.js handles the 3D rendering and travels with the project, and everything else is generated by code rather than loaded as an image or a model.

I built it in about ten hours across a holiday, to see what a model does with a demanding reviewer on the content and new ground on the craft. I read a lot of history and I know this city, so every historical claim the game makes went past someone able to check it.

A narrow alley in the lower town during the 1543 siege
1543. The lower town, where the street layout can only be a conjecture.

The story

I started with 1543, and a playable version existed after about three hours. It was rough, but it worked well enough that friends played it and got to the end. What made it go quickly is that the sources barely exist. No plan gives the fine street layout of the lower town before 1680, so the alleys in the game can only be a coherent conjecture, and the bar to clear is whether they obey the rules that shaped a medieval Mediterranean town.

Where accuracy did matter, the model held the line on its own. The map is the Nice of 1543 rather than the Nice of today, which is most of the pedagogical value. The windows have full wooden shutters instead of the louvred ones that became the signature of the old town two centuries later.

Every place can be questioned in two voices, the character answering with what a Niçois of 1543 could plausibly know and getting it wrong in good faith now and then, and a historian voice that separates the attested fact from the tradition and the legend. I had almost nothing to correct in any of it. This was the part I could review closely, and it came back better than what I would have specified in detail.

Dialogue panel offering a choice between asking the character and asking the historian
Two voices on every question, the man on the gate and the historian.

Then came 1860, which looked like the easier of the two and turned out to be the harder. The street layout is the one still in place today and the public buildings are documented, so the map is a survey rather than a conjecture. That is exactly what makes it difficult, because every departure from the record becomes measurable. Buildings came out in the wrong place, facing the wrong way, and badly arranged in relation to each other, and a few landmarks never quite got there.

Most of the ten hours went into that map, in the round trips it took to get a first version to a level I would call credible.

A square with a fountain in the 1860 old town, ochre facades and louvred shutters
1860. Wider plots, louvred shutters, and a layout that can be checked against the record.

Output

Each episode runs on the same spine. Three objectives have to be completed before a guarded passage opens, and a final exchange closes the story.

Around that spine:

  • Two views, one over the shoulder at eye level and one from above for orientation.
  • A notebook that records one historical fact per place visited.
  • Multiple-choice puzzles, with a hint available at any time.
  • The two-voice panel, which answers from a set of questions and answers written into the project rather than by calling a model live.
  • Portraits drawn by code on a canvas from a handful of traits, so that no image file is needed.
Overhead view of the 1860 old town with the dome of Sainte-Réparate
The overhead view, used for orientation, with the dome of Sainte-Réparate at the centre.

The whole project is a folder of plain files:

nissa/
  index.html            the page you double-click
  js/
    game.js             engine: camera, movement, interface, progress
    world-lib.js        shared scenery machinery, geometry and collisions
    world.js            the 1543 map
    world-1860.js       the 1860 map
    portraits.js        character faces drawn on a canvas
    ask.js              the two-voice panel
    three.min.js        Three.js, shipped with the project
  episodes/
    1543.js             places, dialogue, puzzles and epilogue
    1860.js             the same for 1860
    1543-questions.js   the written questions and answers, both voices
    1860-questions.js   the same for 1860
  _test.html            the automated playthrough

Adding a place means adding one entry to a list in the episode file, and the scenery, the marker, the notebook entry and the map all follow on their own. That separation between content and machinery only became real when 1860 arrived and the engine had to carry two worlds at once.

I split building and reviewing between models. Fable did the development, and a second pass with Opus or Sonnet went back over the historical material and the finer corrections, which is the same auditor and executor split I use on work projects.

_test.html is an automated playthrough. It opens the game in a copy of Chrome running with no window on screen, plays an episode from beginning to end and prints what went wrong. I never asked for it. It came out of a single instruction given early on, that the game had to stay playable.

What it checks:

  • the episode can be finished, every objective completed and the epilogue reached
  • the final area stays sealed until the objectives are done, so the sequence cannot be bypassed
  • every place is reachable on foot, and none is buried inside a building or dropped below sea level
  • no surface is given an impossible colour, which would otherwise render as a silent black face

It reports problems only, so a report containing nothing but counters is a good report, and it is the reason the map stayed changeable as long as it did.

Learnings

The episode with almost no surviving sources came out better than the one with a documented record, and the difference is what the map could be measured against. In 1543 there is nothing to compare it to, so the map only has to be plausible, and the model can judge that for itself from the rules by which a medieval Mediterranean town grew. In 1860 the record makes every departure visible, and closing those gaps takes someone who can say which one to fix and how.

That is the point where my input stopped being useful. I could see at a glance that Sainte-Réparate was facing the wrong way, and I know that church well enough to be sure of it. What I could not do was say what to change, so a correction I had raised as a small one went through round after round. Each attempt disturbed something next to it, the model lost its footing in its own successive changes, and I did not have the words to guide it back.

I wanted something real to play in very little time, and I got it over a holiday, with friends finishing an episode the same evening. What the successive corrections show is that a stable system does not come out of stacked shortcuts, and the debt announces itself exactly where you would expect, in how hard it becomes to make a small change. Working stage by stage, with milestones I could check one at a time, would produce a much better second version.

Why it matters professionally

The project confirmed a principle I already work by. The quality of AI-assisted work follows the quality of the criterion it is given, and the two best results here both came from a stated requirement, that the game had to be playable and that the history had to separate fact from legend. Where I could name the standard the model met it and built its own verification, and where I could only sense that something was off, iteration alone did not close the distance. The consequence for Ops work is that the effort belongs in the definition and the milestones rather than in the speed of the first result, which is the same argument as defining a metric once and having every consumer query that definition instead of recomputing it. A model earns its keep when it can check its own output against a criterion, or challenge an existing plan before implementing it, rather than when I ask for a finished result in a single run.

Nissa stays a side project, and the next episodes will be less ambitious about moving through space, since that is what made this map expensive.