Mohammad Mohammad

Mohammad Mohammad

Open to Summer 2027 internships

I build gameplay systems in C++ and Unreal Engine 5, and the tools and tests that keep them honest.

Computer science at Carleton University in Ottawa, graduating May 2028. By day I build C# test automation for the Department of National Defence.

LinguaSpy, my Unreal Engine 5 C++ spy game: answer the locals in their language or lose your cover.

Watch the 80-second trailer and read how it works

  • 4 cities, 6 missionsin LinguaSpy, my UE5 C++ spy game
  • 23 automated testsrun headless on its gameplay and learning core
  • 100% at 150 msof on-screen hits registered with my lag compensation

Work

  • Hitreg Lab

    A UE5 C++ first-person test range for how shots feel and register: data-driven weapons, controller aim assist, client prediction with server-side rewind lag compensation, a peeker's-advantage meter and a kill cam, all measured over a dedicated server.

    A UE5 C++ FPS lab: lag compensation, peeker's advantage, aim assist and a kill cam, all measured.

    Unreal Engine 5.8, C++. Nov 2026.

  • LinguaSpy

    A top-down spy game about learning a language. You keep your cover in Paris, Madrid, Tokyo or Rome by answering locals in French, Spanish, Japanese or Italian.

    A UE5 C++ spy game: keep your cover by answering locals in French, Spanish, Japanese or Italian.

    Solo. Unreal Engine 5.8, C++. Sept to Nov 2026.

  • Raytracer

    A CPU path tracer in C++17, started from Ray Tracing in One Weekend. I fixed five sampling and shading bugs and added a binned-SAH BVH, OpenMP rendering and Russian roulette.

    A C++17 path tracer. I fixed five rendering bugs and added a SAH BVH, OpenMP and Russian roulette.

    Solo. C++17, CMake. Oct 2025, fixes and BVH Nov 2026.

Hitreg Lab

A first-person test range in Unreal Engine 5 and C++ for studying how shots feel and how they register. It has data-driven weapons, controller aim assist, client-predicted firing with server-side rewind lag compensation, a peeker's-advantage meter and a kill cam. Every system is measured over a dedicated server with two clients, not just built.

38 seconds: lag compensation at 150 ms ping (rewound hitboxes green, current red), then off, the measurements, and the kill cam.
  • 100%of on-screen hits registered up to 150 ms ping with lag compensation, vs 23–30% without
  • 76 → 247 mspeeker's advantage at 60 Hz as round trip goes from 0 to 150 ms (160 measured trials)
  • ~4 msclock-sync error under 40 ms jitter; the engine's own clock was off by 40–70 ms
Bar chart: with lag compensation, 100% of on-screen hits registered at 0, 50, 100 and 150 ms round trip, and 34% at 250 ms where the 200 ms rewind cap applies. Without it: 68%, 30%, 23%, 27% and 13%.
Perfect aim at what the client draws, single taps. Share the server confirmed.
Line chart: peeker's advantage falls as tick rate rises from 20 to 128 Hz and grows with round-trip time, from 45 ms (0 ms, 128 Hz) to 365 ms (150 ms, 20 Hz).
Lag compensation doesn't remove peeker's advantage. Tick rate and interpolation shrink it; latency dominates.

LinguaSpy

Solo projectPrototype v0.6

A top-down spy game about learning a language, in Unreal Engine 5.8 and C++. You're undercover in Paris, Madrid, Tokyo or Rome and keep your cover by answering locals in French, Spanish, Japanese or Italian. Wrong answers drain the cover meter. Blow it and you're sent back to drill the phrases you missed.

What's in the C++: a sentence grader that marks each word and spots accent-only mistakes, a Leitner spaced-repetition scheduler, a policy that picks the exercise type from how well you know a phrase, branching dialogue and mission graphs stored as data assets, a versioned save format with migrations, and a Slate editor tab for authoring conversations. 23 automation tests cover the pure logic.

Engine
Unreal Engine 5.8
Code
C++ game and editor modules, 23 automation tests, Python editor tooling
Built
Sept to Nov 2026
Build
Windows build and 80-second trailer

Four cities, one set of systems

Paris in French

Paris: the spy at a café counter. The barista asks what she can get you, and the reply is being built from French word tiles while the handler says to start with a greeting.
  • 9 conversations, 2 missions, 2 assessment forms
  • Voiced offline with Piper TTS
  • Operation First Contact: order a coffee from Camille at the café

Madrid in Spanish

Madrid: the spy at a tapas bar. The waiter asks if you want something to drink, and the tiles Un agua por favor are all marked correct.
  • 9 conversations, 2 missions
  • Text only for now; Spanish voices are on the list
  • La Tapa Secreta at the tapas bar; El Palacio is the A2-level mission

Tokyo in Japanese

Tokyo: the spy at a konbini. Japanese tiles kudasai, o, onigiri carry romaji readings, and the wrong tiles are highlighted.
  • 3 conversations, 1 mission
  • Konbini Contact: buy an onigiri from Sato-san
  • Unspaced chunk tiles with optional romaji readings; text only because of the voice licence

Rome in Italian

Rome: the spy meets Giulia in a sunlit piazza. She asks Posso aiutarla?, the handler translates, and the reply Scusi dov'è la fontana is being built from Italian word tiles.
  • 3 conversations, 1 mission
  • Protocollo Espresso: ask Giulia the way to the fountain
  • Text only for now; Operazione Colosseo is still to be built

Answer a local

TypeScript port of the game's C++ grader and scheduler, tested against the same cases. The game itself is C++; this runs in your browser so you can try the rules without installing anything.

No French needed: leave out a word, or drop an accent, and watch what the grader says. The six boxes underneath are the Leitner scheduler; the clock buttons skip ahead so phrases come due.

Raytracer

A CPU path tracer in C++17, extended from Peter Shirley's Ray Tracing in One Weekend with triangles and OBJ meshes, light sampling, normal maps and JSON scenes. Version 2 adds a binned-SAH BVH, OpenMP rendering, Russian roulette, fixes for five sampling and shading bugs, and a WebAssembly build that runs right here in your browser.

Five spheres on a checkerboard floor: matte red, normal-mapped brick, clear glass, gold and chrome, with two small glowing spheres in front. Rendered by my path tracer.

Static renderhero_materials.json, 960×540, 1024 samples per pixel, from the WebAssembly build of my path tracer. Press the button to watch it render here, on your CPU.

Converging

Scroll, or drag the slider: the same scene from 1 to 256 samples per pixel.

The materials scene at 256 samples per pixel: smooth shading, clean glass and metal reflections.
Each pass adds one random light path per pixel and averages it in. The noise falls off roughly as one over the square root of the sample count, so 4 spp has half the noise of 1 spp, and 256 has a sixteenth.

Three of the fixes, side by side

Drag across the image. Each pair is the same scene rendered from the commit before and the commit after the fix.

Random number range

Before the fix: the fuzzy metal sphere looks almost like a sharp mirror and the out-of-focus spheres are only partly blurred.
Before b79885c
After the fix: the fuzzy metal reflection is properly blurred and the near yellow sphere has a full, centred depth-of-field blur.
After 351cc24

random_float kept its distribution in a function-local static, so it kept the range of its first call, [0, 1). Every later request for [-1, 1) got [0, 1) too, and random points in the unit sphere and disk came from one octant only. Compare the fuzzy metal sphere (almost a sharp mirror before) and the defocus blur on the near yellow sphere.

Transposed TBN matrix

Shading-normal debug view before the fix: the colours on the normal-mapped spheres do not match their orientation.
Before b79885c
Shading-normal debug view after the fix: sphere tops are green, right sides red and camera-facing sides blue, with brick bevels on top.
After 3c6446a

Debug view: colour is 0.5 × (n + 1). glm::mat3 takes columns, so typing T, B and N in row by row built the transpose, the world-to-tangent matrix. After the fix every sphere is green on top (+y), red on the right (+x) and blue toward the camera (+z).

Direct light, 1/π

Material test scene before the fix: everything is brighter and the glass and chrome spheres look partly matte.
Before 8d651dd
The same scene after the fix: diffuse surfaces are darker, and glass and chrome only show what they reflect and refract.
After 704fd0c

The light-sampling term computed radiance × cos / pdf with no 1/π, so direct light was π times too bright, and it was added to mirrors and glass, which made them look partly matte. Now it is (albedo / π) × L × cos / pdf, for diffuse surfaces only.

The BVH, measured

Without a BVH every ray tests every object. Mine bins primitive centroids into 16 buckets per axis, picks the split with the lowest surface-area cost, stores the tree in one flat array and walks it near child first.

Rays cast per second by bvh_bench against a bumpy sphere tessellated into more and more triangles. Measured on an Intel Core i5-14400F, Windows 11, one thread, WebAssembly build (wasm32-wasi, clang -O2) running in Node 24.20. Native builds are faster; the ratios are the point. Brute force ran on a subset of the same rays for the big meshes, and its hit distances matched the BVH for every ray.
All measured values
TrianglesSAH build (ms)BVH nodesBVH, SAH (Mrays/s)BVH, midpoint (Mrays/s)Brute force (Mrays/s)Speed-up, SAH vs brute force
1280.71238.0158.4220.970748×
1,0581.41,1255.343.9530.0813666×
8,19214.78,8053.0552.8330.00982311×
32,76848.535,6812.862.2380.002381,202×
131,072244.2142,8111.7031.5460.000483,513×
524,2881070.7570,3071.5431.3930.0001311,585×

A whole render of hero_knot.json (17,287 objects, smooth-shaded knot, 320×180, 4 spp) took 0.44 s with the BVH and 111.0 s without it, about 250× faster. The two output images were byte-identical.

About

I'm a computer science student at Carleton University (B.C.S. Honours, AI and Machine Learning stream), graduating in May 2028, and I want to spend my career on the code that decides how a game feels in your hands.

I play Black Ops 6 and Overwatch, and that's a big part of why I'm aiming at gameplay programming: movement, gunplay and hit registration are the systems I most want to build.

My day job is testing other people's software. Since May 2026 I've been a QA automation developer at the Department of National Defence, on a C# framework that turns recorded interactions into Excel-driven Selenium tests. I built its debug mode: a failed run pauses, the tester fixes the step in the spreadsheet, and the run resumes from the last passing step. Before that, from January to April 2026, I was an IP network engineering intern at Nokia, writing Python and Tcl regression suites for protocol state machines. Both jobs left me with a habit you'll see across these projects: if a system matters, it gets an automated test.

Also on this site: TitanInfer, a C++20 neural-network inference library with a cache-blocked AVX2 matrix multiply.

The fastest way to reach me is email: mohpresentations@gmail.com. My résumé is a PDF, my code is on GitHub, and my work history is on LinkedIn.