Game ·
Oh! My Dog!
A vet game where the finding that cracks the case is free and the expensive test only confirms it, built on a suite that once passed every check on a patient who could not exist.
A consultation arrives with an owner, a budget and a clock. You work out what is wrong before spending money you cannot get back, then recommend a treatment the owner is free to refuse.
My wife is a vet. The cases come from what she comes home with: the owner who read a forum and wants a scan for a slipped disc, the one who is certain it is the food because they changed the recipe, the cat that will not be handled and takes a chunk out of you while you try.

Everything at the table is free. Everything at the computer has a price and a duration. At the end of the day the practice writes off whatever the owners could not pay.
The whole game makes one argument: the finding that cracks a case is usually free. Lifting a cat's tongue is diagnostic for a linear foreign body and costs nothing. Reach for imaging first and you have spent the surgery money confirming what a torch would have shown you. So the skill being tested is knowing where to look, not knowing what to buy, and anything that undermines that is probably wrong however clever it is.
Every test passed on a patient who could not exist
The consult screen once presented a urethral obstruction in a seven year old spayed female cat.
Urethral obstruction is a condition of male cats. The blockage forms in a narrow urethra that female cats do not have. It is not a rare presentation or an interesting edge case. It cannot happen.
All sixty tests passed while that was true. The assembler matched conditions to patients by species alone, so it checked that a cat condition went to a cat and stopped there. Every test asserted the thing the code did rather than the thing the code was for.
Conditions now declare what they require, sex and whether the animal is entire, and the assembler honours it. The part worth keeping is the other half: a condition that no patient in the database could ever present with is a startup error, not a warning. If the requirements are so tight that nobody can arrive with it, that is a content bug and the game refuses to open.
That is the same failure as a health check returning a cheerful 200 because it never calls the database it claims to be checking. The suite was green, the dashboard was green, and the only thing being tested was that the endpoint existed.
The prices live in one file
No condition states a price. procedures.json is the vet's toolkit defined once: a general examination is two minutes and free, bloods are eighty five pounds and two minutes, an ultrasound is two hundred and forty and four.
Change the cost of a radiograph and every one of the sixty seven conditions changes with it. This is deliberately the opposite of the sibling colony project, where the same casing colour forked into three values across a hundred and fourteen generator scripts because nobody owned it.
The same rule runs through everything tunable. The difficulty curve is a table of levers interpolated across a twelve day loop. The appointment length is one number in that table. When I played it and found I was running over every single day, the fix was editing a five to a ten, not touching code, and the tests assert that the curve implements the table rather than pinning the numbers in it.
An operation is a JSON file
Ten of the conditions have surgery as the correct answer. Choosing one used to cut straight to a debrief, so the most dramatic thing a vet does happened off screen in a text box.

Induce, then find the abnormal thing, then hold the animal steady while you deal with it. The room is the same room as the consultation, so there is nothing new to learn about where anything is.
Each operation is six to ten steps in its own file. A shared file wraps the start and the end around it, so a new operation never repeats an incision. Every kind of thing that can go wrong lives in one more file with what answers it, how long ignoring it takes to kill, and what each harm costs. Adding a complication is an entry in that file and an answer step, and no code at all.
The anaesthetic knows nothing about steps and the steps name no complication. When something goes wrong the surgery asks the table what is happening rather than deciding for itself.
Say what the jargon means
Theatre is the one room that speaks its own language, and "enterotomy over healthy bowel" is a guess rather than a decision for anybody who is not a vet.
So every step, every wrong way of doing it, and every structure on an identify screen carries one plain line underneath. The term stays, because the term is worth learning. A test fails any step that does not have one.
The dial holds a depth, it does not read one
The animal drifts off the dial, and further the longer it has been under, so the vaporiser has to be tended rather than set once. It loses heat too, faster through an open abdomen, which is what a long operation costs. The heat mat is free and only helps from the moment it is on, so what it costs is thinking of it.
Three things fell out of building this, and all three were wrong first.
Hold two, not read two. A saturating animal needs more gas at minute twenty five than at minute five, so a remedy that set a literal number put the dial somewhere that held nothing.
An answer may be given as often as the trouble returns. Every other step happens once. These are what you do about the animal, and it can go too deep twice.
Name the cause worst first. A drifting animal is routinely both too deep and going cold. Announcing the cold offered a heat mat while the plane went on sinking. Announcing the plane read a cold animal's low pressure as a deep one.
Nothing is timed by reflex. The anaesthetic advances when you act, exactly as the clock advances when you run a procedure. Thinking is free, being wrong is not, and a real vet's expertise stays worth something in a way a dexterity game would throw away.
A breed you cannot draw at thirty pixels
The sprites are generated with my own tool, at about twenty pence a draw, which makes a new species a sentence rather than a commission.
My wife asked for a Pomeranian and a Chihuahua by name. The Chihuahua worked first time. The Pomeranian took five attempts and about a pound and still reads as a generic fluffy dog.
The lesson is not about prompting. A Chihuahua is enormous upright ears, which is silhouette. A Great Dane is size, which is silhouette. A beagle worked once its three colours were named, which is pattern. A Pomeranian is fur texture and a tail curled over its back, and texture needs pixels there are none spare of. Drawing it in profile produced a convincing squirrel facing the wrong way.
The honest options are keeping a correct generic dog or keeping a specific wrong one. Neither is another draw.
The deploy was right and the players were on yesterday's build
This one belongs on this site more than any of it.
The web version is a Godot export sitting in R2 behind a Worker. Publishing was verified: the live file's etag matched the local build byte for byte, every time.
Players were still on the previous version. Godot's progressive web app option installs a service worker that serves the build it already has and fetches the new one for next time, so a reload does not fix it, because the reload is what fetches the update. It put a phone and a browser one version behind on the same afternoon, an hour after each had been published to correctly, and the second time I took it for a failed deploy and went looking in the wrong place.
Offline play is worth less than players being on the game that shipped, so there is no service worker now. Turning it off only stops new visitors getting one, so the page unregisters any that already exist and deletes their caches.
Two more things came out of chasing it. The export never cleared its own output folder, so fifteen dead files were being re-uploaded on every publish, including the service worker itself, which would have gone on being served after I turned it off. And the phone build had drifted four versions behind the web one, because "publish" had quietly meant "publish the browser version" and nothing said otherwise. Publishing now does both, checks what each one actually ended up running, and fails loudly rather than reporting a success it has not confirmed.
Screenshots catch what tests cannot
There are 1,566 checks and they never load the main scene, so a parse error there passes every one of them. Layout, colour, clipping and "the panel collapsed to its minimum width" are invisible to all of them too.
So the game photographs itself. Flags drive it to any screen, at a pinned seed, and shoot it. The shutter waits for the screen to finish arriving rather than guessing at a delay, because speech types itself out and a shot timed early came back reading "there are ele" and sent somebody looking for a wrapping bug that did not exist.
A screen no flag can reach is a screen that ships broken. The identify screen ran off the side of a phone for as long as it existed, because nothing could photograph it.
Where it goes
It is free in the browser and it will stay free while it is in beta. Sixty seven conditions, eight species, four operations, and not one of them checked yet by anybody qualified. The gate for that is built and works: conditions carry a review status, and a single flag stops unreviewed medicine reaching a player. It is on, because turning it off today would empty the game.
That is the last thing that should happen before this goes anywhere as a product. Once it has, it goes to itch.io and to phones at a pound or two.
Play it at ohmydog.dmooney.engineer. It runs in any browser, desktop or phone, with nothing to install and no sign up. If you work in the profession and something reads wrong, I would rather hear it.