diff --git a/backend/src/api.ts b/backend/src/api.ts
index 39605ba8eddd7788ccb4c75eaf5698e9a4b8ff64..df68b72576b9561b4d9ce78a306217391505f30c 100644
--- a/backend/src/api.ts
+++ b/backend/src/api.ts
@@ -177,7 +177,7 @@ const apiPlugin: FastifyPluginCallback = (fastify, options, next) => {
     handler: async (request, reply) => {
       const shots = await connection
         .table("shots")
-        .insert(request.body)
+        .insert({ game_id: request.params.id, ...request.body })
         .returning<Types.Shot[]>("*");
 
       reply.code(200).send(shots[0]);
diff --git a/backend/src/schemas.ts b/backend/src/schemas.ts
index 64068e7e1db852cc37c1a28cb8e4f21c9ac5e731..b409b29b2a235ed8642563a5386c9c995202add8 100644
--- a/backend/src/schemas.ts
+++ b/backend/src/schemas.ts
@@ -35,7 +35,16 @@ export const Shot = Type.Object({
   final: Type.String(),
 });
 
-export const GameUpdate = Type.Omit(Game, ["id"]);
-export const ClueCreate = Type.Pick(Clue, ["word_id"]);
-export const ClueUpdate = Type.Pick(Clue, ["began_at", "ended_at"]);
+export const GameUpdate = Type.Partial(
+  Type.Pick(Game, ["began_at", "ended_at"]),
+);
+
+export const ClueUpdate = Type.Partial(
+  Type.Pick(Clue, ["began_at", "ended_at"]),
+);
+export const ClueCreate = Type.Intersect([
+  Type.Pick(Clue, ["word_id"]),
+  ClueUpdate,
+]);
+
 export const ShotCreate = Type.Omit(Shot, ["id", "game_id"]);
diff --git a/frontend/src/js/fight_scene.ts b/frontend/src/js/fight_scene.ts
index e26e475880361603067ef7948052d2da97329276..47741fde94b3cefde17f7879ab290aa2ff342936 100644
--- a/frontend/src/js/fight_scene.ts
+++ b/frontend/src/js/fight_scene.ts
@@ -122,7 +122,6 @@ export default class FightScene extends Phaser.Scene {
     this.beGame = (
       await backend.updateGame(this.beGame.id, {
         began_at: new Date().toISOString(),
-        ended_at: null,
       })
     ).data;
 
@@ -230,7 +229,6 @@ export default class FightScene extends Phaser.Scene {
   async endGame() {
     this.beGame = (
       await backend.updateGame(this.beGame.id, {
-        began_at: this.beGame.began_at, // TODO: make this optional in the type
         ended_at: new Date().toISOString(),
       })
     ).data;
@@ -238,11 +236,11 @@ export default class FightScene extends Phaser.Scene {
     this.scene.start("game_over");
   }
 
-  showSubmitFeedback(color: string) {
+  showSubmitFeedback(color: string, input: string) {
     const text = this.add.text(
       this.cameras.main.width / 2,
       this.cameras.main.height / 2,
-      this.hud.input.text,
+      input,
       {
         font: "bold 64px Courier",
         color: color,
@@ -300,21 +298,29 @@ export default class FightScene extends Phaser.Scene {
   }
 
   submitTranscription(inputStatus: InputStatus) {
+    // NOTE: this ain't async to avoid any UX delay
     const { score, match } = this.findMatchingFoe(inputStatus.final);
-    // TODO: visual near misses based on score
+    backend.createShot(this.beGame.id, {
+      clue_id: match?.beClue?.id || null,
+      ...inputStatus,
+    });
     if (match === null) {
       // NOOP
-      this.showSubmitFeedback("#FFFFFF");
+      this.showSubmitFeedback("#FFFFFF", inputStatus.final);
     } else if (score < 0.9) {
+      // TODO: visual near misses based on score
       this.updateScore(-1);
       match.handleFailure();
-      this.showSubmitFeedback("#FF0000");
+      this.showSubmitFeedback("#FF0000", inputStatus.final);
       new Spear(this, this.player, undefined);
     } else {
+      backend.updateClue(match.beClue.id, {
+        ended_at: new Date().toISOString(),
+      });
       this.updateScore(+10);
       this.popFoe(match);
       match.handleSuccess();
-      this.showSubmitFeedback("#00FF00");
+      this.showSubmitFeedback("#00FF00", inputStatus.final);
       new Spear(this, this.player, match.critter);
       // TODO: increase score
     }
@@ -323,17 +329,17 @@ export default class FightScene extends Phaser.Scene {
   createAndBindTypewriter() {
     this.typewriter ??= new Typewriter();
     this.typewriter.setHidden(this.game.device.os.desktop);
-    this.typewriter.onSubmit = (inputStatus) => {
+    this.typewriter.onSubmit = async (inputStatus) => {
       if (inputStatus.began_at === null) return;
       if (inputStatus.ended_at === null) return;
       if (inputStatus.final === "") return;
+      this.hud.input.text = "";
       this.submitTranscription({
         began_at: inputStatus.began_at.toISOString(),
         ended_at: inputStatus.ended_at.toISOString(),
         typed: inputStatus.typed,
         final: inputStatus.final,
       });
-      this.hud.input.text = "";
     };
     this.typewriter.onChange = (inputStatus) => {
       this.hud.input.text = inputStatus.final;
diff --git a/frontend/src/js/foe.ts b/frontend/src/js/foe.ts
index 04baaa1f05d255952196a2b273801b7c5fb10b77..411b3d9a7401ac586d335ada4b1586d4468eb5cb 100644
--- a/frontend/src/js/foe.ts
+++ b/frontend/src/js/foe.ts
@@ -21,17 +21,12 @@ class Foe {
   async initialize() {
     this.beWord = (await backend.getWord()).data;
     if (!this.scene.scene.isActive()) return;
-    // this.beClue = (
-    //   await backend.createClue(this.scene.beGame.id, {
-    //     word_id: this.beWord.id,
-    //   })
-    // ).data;
-    // this.beClue = (
-    //   await backend.updateClue(this.beClue.id, {
-    //     began_at: new Date().toISOString(),
-    //     ended_at: null,
-    //   })
-    // ).data;
+    this.beClue = (
+      await backend.createClue(this.scene.beGame.id, {
+        word_id: this.beWord.id,
+        began_at: new Date().toISOString(),
+      })
+    ).data;
 
     this.clue = new Clue(this.scene, this.beWord);
     this.critter = new Critter(this.scene);