diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 0c7da4d35dcb6a7ff428bbba1ee4822e13478a69..041b653a569063f0cad45fe8dcb1d28a4aa9e319 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,6 +11,7 @@
       "dependencies": {
         "axios": "^0.26.0",
         "damerau-levenshtein": "^1.0.8",
+        "newton-raphson-method": "^1.0.2",
         "phaser": "^3.55.2"
       },
       "devDependencies": {
@@ -9222,6 +9223,11 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
+    "node_modules/newton-raphson-method": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/newton-raphson-method/-/newton-raphson-method-1.0.2.tgz",
+      "integrity": "sha512-UoBJJxNVVQhviRvled1bL76IjmPls4dNLPeF661FW2eNrv/pKKfRDpOdlN+vo9XamZvj8OMt/KtjOBTFN6I0gw=="
+    },
     "node_modules/nice-try": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -21973,6 +21979,11 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
+    "newton-raphson-method": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/newton-raphson-method/-/newton-raphson-method-1.0.2.tgz",
+      "integrity": "sha512-UoBJJxNVVQhviRvled1bL76IjmPls4dNLPeF661FW2eNrv/pKKfRDpOdlN+vo9XamZvj8OMt/KtjOBTFN6I0gw=="
+    },
     "nice-try": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 4c280c396a51cf7baa5d9f93d8ad54239ea7625a..4b088d87f94a1af2b0b63fdbec5cc39865830c31 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,6 +19,7 @@
   "dependencies": {
     "axios": "^0.26.0",
     "damerau-levenshtein": "^1.0.8",
+    "newton-raphson-method": "^1.0.2",
     "phaser": "^3.55.2"
   },
   "devDependencies": {
diff --git a/frontend/src/js/fight_scene.js b/frontend/src/js/fight_scene.js
index d301a88ee8a789e973328007c11c3de1bae1a108..0816e9ffdd3ee26208fc297c83bb5e5a14e546ca 100644
--- a/frontend/src/js/fight_scene.js
+++ b/frontend/src/js/fight_scene.js
@@ -1,5 +1,7 @@
 import Phaser from "phaser";
 
+import nr from "newton-raphson-method";
+
 import Foe from "./foe";
 import backend from "./backend";
 
@@ -8,8 +10,10 @@ import levenshtein from "damerau-levenshtein";
 const fightScene = new Phaser.Scene("fight");
 
 fightScene.foes = [];
+fightScene.spears = [];
 fightScene.preload = preload;
 fightScene.create = create;
+fightScene.update = update;
 
 function setAnimation(obj, idleKey) {
   obj.play({ key: idleKey, repeat: -1 });
@@ -222,23 +226,11 @@ function initAndBindGuessPreview(scene) {
 }
 
 function shootSpear(enemy, hit, scene = fightScene) {
-  let startPoint = new Phaser.Math.Vector2(scene.player.x, scene.player.y);
-  let controlPoint1 = new Phaser.Math.Vector2(
-    enemy.x + (scene.player.x - enemy.x) / 2,
-    scene.cameras.main.height - (scene.player.x - enemy.x),
-  );
-
-  let endPoint = new Phaser.Math.Vector2(
-    enemy.x + enemy.width * 1.6,
-    scene.cameras.main.height - 60,
-  );
   let message = scene.add
     .sprite(scene.cameras.main.width / 2, scene.cameras.main.height / 2)
     .setScale(1);
 
-  // hit / missing <--- message
   if (!hit) {
-    endPoint = new Phaser.Math.Vector2(-500, scene.cameras.main.height - 60);
     message.play({ key: "missing", repeat: 1 });
     message.on("animationcomplete", () => {
       message.anims.remove("miss");
@@ -257,70 +249,47 @@ function shootSpear(enemy, hit, scene = fightScene) {
     scene.foes.splice(scene.foes.indexOf(enemy), 1);
   }
 
-  let curve = new Phaser.Curves.QuadraticBezier(
-    startPoint,
-    controlPoint1,
-    endPoint,
-  );
+  let spear = scene.add.sprite(scene.player.x, scene.player.y, "spear");
+  scene.spears.push(spear);
+  scene.physics.world.enable(spear);
+  scene.physics.add.collider(spear, scene.ground);
+  spear.body.setBounce(0.2);
+
+  const dx = scene.player.x - enemy.x;
+  const dy = 0;
+  const v = 450; // MAGIC NUMBER
+  const w = 100;
+  const g = 200;
+  // TODO: maybe introduce damping
+  // TODO: expand and use analytic derivative
+  const f = (theta) =>
+    2 * dy * Math.pow(w - v * Math.cos(theta), 2) +
+    2 * v * Math.sin(theta) * (w - v * Math.cos(theta)) * dx +
+    g * Math.pow(dx, 2);
+
+  const theta = nr(f, Math.PI, {
+    verbose: true,
+    maxIterations: 100,
+  });
 
-  let graphics = scene.add.graphics();
-  graphics.lineStyle(1, 0xff00ff, 1);
-
-  // curve.draw(graphics); // decomment to see the trajectory
-
-  let spear = scene.add.follower(curve);
-
-  // console.log(player.x + " " + enemy.x);
-  if (hit) {
-    spear.startFollow({
-      positionOnPath: true,
-      duration: (scene.cameras.main.width - enemy.x) * 3,
-      rotateToPath: true,
-      verticalAdjust: true,
-      scale: 3,
-      onComplete: (x, y) => {
-        let spearHitObj = scene.add
-          .sprite(
-            enemy.x + enemy.width * 1.6 - 10,
-            scene.cameras.main.height - 60,
-          )
-          .setScale(2);
-        setAnimation(spearHitObj, "spearHitAni");
-        spear.anims.stop("spearAni");
-        spear.anims.remove("spearAni");
-        spear.destroy();
-        setTimeout(() => {
-          enemy.flipX = false;
-          setAnimation(enemy, enemy.typeName + "_run");
-          enemy.movement.stop();
-          scene.tweens.add({
-            targets: enemy,
-            x: -500,
-            duration: 5000,
-            onComplete: function () {
-              enemy.destroy();
-              //callback(enemy); // ???
-            },
-          });
-        }, 3000);
-        setTimeout(() => {
-          spearHitObj.destroy();
-        }, 4000);
-      },
-    });
-  } else {
-    enemy.setInteractive();
-    enemy.movement.resume();
-    setAnimation(enemy, enemy.typeName + "_walk");
-    spear.startFollow({
-      positionOnPath: true,
-      duration: 3000,
-      rotateToPath: true,
-      verticalAdjust: true,
-      scale: 3,
-    });
+  const t = dx / (w - v * Math.cos(theta));
+
+  if (theta) {
+    spear.body.setVelocity(v * Math.cos(theta), v * Math.sin(theta));
   }
 
+  scene.physics.add.overlap(spear, enemy, (player, nemico) => {
+    scene.physics.world.removeCollider(this);
+    // TODO: fancy bounce
+    scene.spears.splice(scene.spears.indexOf(spear), 1);
+    spear.destroy();
+    // TODO: refactor into flee method
+    nemico.play(nemico.species + "_run");
+    nemico.flipX = false;
+    nemico.body.setVelocity(-200, 0);
+    setTimeout(() => nemico.destroy(), 2000);
+  });
+
   spear.scale = 2;
   spear.anims.play("spearAni");
 }
@@ -346,16 +315,8 @@ function submitTranscription(transcription, scene) {
   // TODO: we can have near misses depending on similarity!
   let hit = similarity >= 0.9;
   let enemy = match;
-  enemy.sprite.disableInteractive();
-  // here invoke image
-  enemy.sprite.movement.pause();
-  setAnimation(enemy.sprite, enemy.sprite.typeName + "_idle");
 
-  // let beginTime = new Date().getTime();
-  // let endTime = new Date().getTime();
-  // let deltaTime = endTime - beginTime;
-
-  shootSpear(enemy.sprite, hit);
+  shootSpear(enemy.animalSprite, hit);
 }
 
 function gameStart(scene) {
@@ -371,4 +332,10 @@ function dispatchEnemy(scene) {
   });
 }
 
+function update() {
+  this.spears.forEach((spear) => {
+    spear.setRotation(spear.body.velocity.angle());
+  });
+}
+
 export default fightScene;
diff --git a/frontend/src/js/foe.js b/frontend/src/js/foe.js
index 1334707bfe842c8a5d23b93533bd27946c7aedd1..a8db37028aa93b739862df8d6d51b117c8e22561 100644
--- a/frontend/src/js/foe.js
+++ b/frontend/src/js/foe.js
@@ -35,8 +35,9 @@ class Foe {
 
     this.animalSprite = this.scene.physics.add
       .sprite(-100, this.scene.cameras.main.height - 100, this.species)
-      .setScale(-1 * scale, scale)
+      .setScale(scale)
       .setInteractive();
+    this.animalSprite.flipX = true;
 
     this.scene.physics.add.collider(this.animalSprite, this.scene.ground);