Surgical Trainer 3D: a browser theatre for medical students by Abdulhamid SonaikeSurgical Trainer 3D: a browser theatre for medical students by Abdulhamid Sonaike

Surgical Trainer 3D: a browser theatre for medical students

Abdulhamid Sonaike

Abdulhamid Sonaike

Surgical Trainer 3D is a browser based surgical training simulator for medical students. Procedures are defined in JSON, the engine reads that JSON and drives the game, and every model in the theatre is built by script rather than downloaded.

The constraint that shaped everything

This is a teaching tool, and the fastest way to make it useless is to let it be confidently wrong. So the rule in the codebase is that no clinical detail gets invented. If a step, a suture size or an instrument choice is uncertain, it goes into the procedure JSON as a todo on that step rather than a guess, every procedure carries reviewed: false until a qualified clinician has checked it, and the interface shows an "Unreviewed content" badge while it does. The educational disclaimer on the home screen and in the pause menu is not removable, by design.

Architecture

The procedure logic lives in one folder that imports nothing from Three.js and nothing from the DOM. Everything Three.js lives in another. The two talk through a flat contract: the scene sends a tool action, meaning a tool id, a zone id, an action and how close the click landed, and gets back a step outcome, meaning whether it was right, what to say, what the mistake was and what it costs. No Three.js type ever crosses that line. That is what lets the rules of play be unit tested in Node instead of needing a browser and a GPU.

Plain Three.js, on purpose

Three.js and Vite with TypeScript in strict mode, a roughly sixty line observable store, a DOM heads up display rather than a component tree, and no backend. No React, no React Three Fiber, no Zustand, no Redux, no Tailwind. That was a decision rather than an omission: a brief proposed all of them, and every one I did not adopt is written down with the reason in a decisions log that now runs to dozens of entries.

Anatomy is code, not downloads

The click zones are pinned to the anatomy, so the anatomy is built by Blender scripts at build time and exported as glTF that the app loads with three's own loader and meshopt decoder. Players never need Blender and the project adds no npm dependency for it. Zone positions are computed from landmarks the body and organ builds write out, so rebuilding the body moves the zones with it instead of quietly leaving them behind.

The tests are about the things that break quietly

A zone laid over a target silently takes its clicks, and a deep structure can be present but unreachable from a slanted camera. Neither of those throws an error. So there is a test that aims at every step's target in turn, and a reachability test that sweeps the view from the camera in use at each step and fails when a target covers less than 0.2 percent of it. Procedures are loaded against the real zones, the real instrument catalogue and the real camera presets, so a mistyped zone id fails a test rather than never matching at runtime.

Performance is a budget, not a hope

The target is 60 frames per second and never below 50 on a mid range laptop with integrated graphics, with the theatre under 150 draw calls. The quality levels were set by measuring rather than guessing: on the target machine Medium came in around 17 ms a frame and High around 53 ms, with ambient occlusion alone accounting for 35 ms at full resolution. High also redraws the scene for normals and goes over the draw call budget, so it is reserved for dedicated graphics. Every geometry, material and texture the scene creates is registered for disposal, because leaking GPU memory across a restart is a real bug here.

What it demonstrates

3D on the web with no framework to hide behind, an architecture chosen so the interesting logic can be tested without a browser, performance treated as a measured budget, and a discipline about not inventing facts in a domain where confidently wrong is worse than saying you do not know yet.
One flat contract between the engine and the scene, which is why the rules of play can be tested without a browser.
One flat contract between the engine and the scene, which is why the rules of play can be tested without a browser.
The failures that never throw an error, and the tests written to catch each one.
The failures that never throw an error, and the tests written to catch each one.
Quality levels chosen by measuring frame time on the target laptop, not by picking numbers that sounded right.
Quality levels chosen by measuring frame time on the target laptop, not by picking numbers that sounded right.
Like this project

Posted Sep 15, 2026

A data-driven 3D surgical simulator in plain Three.js and TypeScript: procedures in JSON, anatomy built in Blender, rules of play tested in Node.