Skip to content

Conversation

@DortyTheGreat
Copy link
Contributor

@DortyTheGreat DortyTheGreat commented Dec 31, 2025

Added more options for Pitch40.
Changed default settings for Pitch40, increasing the average speed (25.5768 → 28.8312).

Even higher average speeds could be achieved with the same pitch lock settings(~30), but using a greater(~100) height difference.

Values were generated using simulated annealing (python). Tested in both singleplayer and on a server (with Grim AntiCheat).

Type of change

  • Bug fix
  • New feature

Description

Added more options for Pitch40.
Changed default settings for Pitch40, increasing the average speed (25.5768 → 28.8312).

Even higher average speeds could be achieved with the same pitch lock settings(~30), but using a greater(~100) height difference.

Values were generated using simulated annealing (python). Tested in both singleplayer and on a server (with Grim AntiCheat).

Related issues

How Has This Been Tested?

Singleplayer and multiplayer.

Checklist:

  • My code follows the style guidelines of this project.
  • I have added comments to my code in more complex areas.
  • I have tested the code in both development and production environments.

Added more options for Pitch40.
Changed default settings for Pitch40, increasing the average speed (25.5768 → 28.8312).

Even higher average speeds could be achieved with the same pitch lock settings(~30), but using a greater(~100) height difference.

Values were generated using simulated annealing (python). Tested in both singleplayer and on a server (with Grim AntiCheat).
@crosby-moe
Copy link
Collaborator

interesting! could you share the simulation code used to generate the values?

@DortyTheGreat
Copy link
Contributor Author

image image image image

@DortyTheGreat
Copy link
Contributor Author

DortyTheGreat commented Dec 31, 2025

python generator (ChatGPT + Heppe's python code + My Brain)

import math
import random

# =========================
# Vector & Physics (YOURS)
# =========================

class Vec3:
    def __init__(self, x=0, y=0, z=0):
        self.x = x
        self.y = y
        self.z = z

    def add(self, x, y, z):
        return Vec3(self.x + x, self.y + y, self.z + z)

    def horizontalDistance(self):
        return math.sqrt(self.x * self.x + self.z * self.z)

    def length(self):
        return math.sqrt(self.x * self.x + self.z * self.z + self.y * self.y)

    def multiply(self, x, y, z):
        return Vec3(self.x * x, self.y * y, self.z * z)

    def copy(self):
        return Vec3(self.x, self.y, self.z)


def lookAngle(xRot, yRot):
    f = xRot * math.pi / 180.0
    f1 = -yRot * math.pi / 180.0
    f2 = math.cos(f1)
    f3 = math.sin(f1)
    f4 = math.cos(f)
    f5 = math.sin(f)
    return Vec3((f3 * f4), (-f5), (f2 * f4))


def elytraPointer(
    ticks,
    xRot=0,
    yRot=-90,
    deltaMovement=Vec3(),
    deltaXRot=0,
    height=float('inf'),
    startingPosition=Vec3(),
    delta2_rot=None
):
    position = startingPosition
    for _ in range(ticks):
        vec3 = deltaMovement
        vec31 = lookAngle(xRot, yRot)
        f = xRot * math.pi / 180.0

        d0 = 0.08
        d1 = math.cos(f)
        d3 = vec3.horizontalDistance()
        d5 = math.cos(f) ** 2

        vec3 = deltaMovement.add(0.0, d0 * (-1.0 + d5 * 0.75), 0.0)

        if d1 != 0:
            if vec3.y < 0.0:
                d6 = vec3.y * -0.1 * d5
                vec3 = vec3.add(
                    vec31.x * d6 / d1,
                    d6,
                    vec31.z * d6 / d1
                )

            if f < 0.0:
                d10 = d3 * (-math.sin(f)) * 0.04
                vec3 = vec3.add(
                    -vec31.x * d10 / d1,
                    d10 * 3.2,
                    -vec31.z * d10 / d1
                )

            vec3 = vec3.add(
                (vec31.x / d1 * d3 - vec3.x) * 0.1,
                0.0,
                (vec31.z / d1 * d3 - vec3.z) * 0.1
            )

        deltaMovement = vec3.multiply(0.99, 0.98, 0.99)
        position = position.add(deltaMovement.x, deltaMovement.y, deltaMovement.z)

        if height + position.y <= 0:
            return position, deltaMovement

    return position, deltaMovement


# =========================
# Infinite Flight Controller
# =========================

class InfinitePitchController:
    def __init__(
        self,
        height_delta,
        pitch_up_speed,
        pitch_down_speed,
        down_angle,
        up_angle
    ):
        self.height_delta = height_delta
        self.pitch_up_speed = pitch_up_speed
        self.pitch_down_speed = pitch_down_speed
        self.down_angle = down_angle
        self.up_angle = up_angle

        self.pitch = down_angle
        self.pitching_down = True
        self.base_height = 0

    def rand(self, base, jitter):
        return base #+ random.uniform(-jitter, jitter)

    def tick(self, y):
        lower = self.base_height
        upper = self.base_height + self.height_delta

        if self.pitching_down and y <= lower:
            self.pitching_down = False
        elif not self.pitching_down and y >= upper:
            self.pitching_down = True

        if not self.pitching_down:
            self.pitch -= self.rand(self.pitch_up_speed, 3.0)
            if self.pitch < self.up_angle:
                self.pitch = self.up_angle
                #self.ticks = 0
                self.pitching_down = True
        else:
            if self.pitch < self.down_angle:
                #self.ticks += 1
                
                #coef = 1 + min(1, self.ticks / 50)
                self.pitch += self.rand(self.pitch_down_speed * 1, 0.125)

        return self.pitch


# =========================
# Simulation
# =========================

def simulate_flight(controller, ticks=10_000):
    pos = Vec3(0, controller.height_delta, 0)
    vel = Vec3(0, 0, 0)

    for _ in range(ticks):
        pitch = controller.tick(pos.y)
        pos, vel = elytraPointer(
            ticks=1,
            xRot=pitch,
            yRot=-90,
            deltaMovement=vel,
            startingPosition=pos
        )
        if pos.y <= -10:
            return False, pos.x

    return True, pos.x


def fitness(ctrl):
    alive, dist = simulate_flight(ctrl)
    return dist if alive else -1e9


# =========================
# Simulated Annealing
# =========================

def clamp(v, lo, hi):
    return max(lo, min(hi, v))


def simulated_annealing(
    steps=2000,
    T_start=1000.0,
    T_end=1.0
):

    '''
        height_delta=80.0, 
        pitch_up_speed=7.04,
        pitch_down_speed=1.10,
        down_angle=38.95,
        up_angle=-63.01
        '''
    current = InfinitePitchController(
        
        # Put some values and code will try to find a better option
        height_delta=40, 
        pitch_up_speed=5.45,
        pitch_down_speed=0.90,
        down_angle=37.72,
        up_angle=-54.77
        # 15199.6
    )

    current_score = fitness(current)
    best = current
    best_score = current_score

    for step in range(steps):
        T = T_start * ((T_end / T_start) ** (step / steps))

        candidate = InfinitePitchController(
            height_delta=clamp(
                current.height_delta + random.uniform(-2, 2),
                40, 100
            ),
            pitch_up_speed=clamp(
                current.pitch_up_speed + random.uniform(-1, 1),
                0.1, 30
            ),
            pitch_down_speed=clamp(
                current.pitch_down_speed + random.uniform(-0.1, 0.1),
                0.1, 10.0
            ),
            down_angle=clamp(
                current.down_angle + random.uniform(-0.5, 0.5),
                20, 60
            ),
            up_angle=clamp(
                current.up_angle + random.uniform(-1, 1),
                -70, -30
            )
        )

        score = fitness(candidate)
        delta = score - current_score

        if delta > 0 or random.random() < math.exp(delta / T):
            current = candidate
            current_score = score

            if score > best_score:
                best = candidate
                best_score = score
                print(f"[NEW BEST] {best_score:.1f}")

    return best, best_score


# =========================
# Run
# =========================

if __name__ == "__main__":
    best, score = simulated_annealing()

    print("\n=== OPTIMAL PARAMETERS (SA) ===")
    print(f"Height delta       : {best.height_delta:.2f}")
    print(f"Pitch up speed     : {best.pitch_up_speed:.2f}")
    print(f"Pitch down speed   : {best.pitch_down_speed:.2f}")
    print(f"Down angle         : {best.down_angle:.2f}")
    print(f"Up angle           : {best.up_angle:.2f}")
    print(f"Distance (10k ticks): {score:.1f}")


@DortyTheGreat
Copy link
Contributor Author

visual.py

import math
import random
import matplotlib.pyplot as plt

# =========================
# Vector & Physics
# =========================

class Vec3:
    def __init__(self, x=0, y=0, z=0):
        self.x = x
        self.y = y
        self.z = z

    def add(self, x, y, z):
        return Vec3(self.x + x, self.y + y, self.z + z)

    def horizontalDistance(self):
        return math.sqrt(self.x * self.x + self.z * self.z)

    def multiply(self, x, y, z):
        return Vec3(self.x * x, self.y * y, self.z * z)


def lookAngle(xRot, yRot):
    f = xRot * math.pi / 180.0
    f1 = -yRot * math.pi / 180.0
    return Vec3(
        math.sin(f1) * math.cos(f),
        -math.sin(f),
        math.cos(f1) * math.cos(f)
    )


def elytraPointer(ticks, xRot, yRot, deltaMovement, startingPosition):
    pos = startingPosition
    vel = deltaMovement

    for _ in range(ticks):
        look = lookAngle(xRot, yRot)
        f = xRot * math.pi / 180.0

        d0 = 0.08
        d1 = math.cos(f)
        d3 = vel.horizontalDistance()
        d5 = math.cos(f) ** 2

        vel = vel.add(0, d0 * (-1 + d5 * 0.75), 0)

        if d1 != 0:
            if vel.y < 0:
                d6 = vel.y * -0.1 * d5
                vel = vel.add(look.x * d6 / d1, d6, look.z * d6 / d1)

            if f < 0:
                d10 = d3 * (-math.sin(f)) * 0.04
                vel = vel.add(
                    -look.x * d10 / d1,
                    d10 * 3.2,
                    -look.z * d10 / d1
                )

            vel = vel.add(
                (look.x / d1 * d3 - vel.x) * 0.1,
                0,
                (look.z / d1 * d3 - vel.z) * 0.1
            )

        vel = vel.multiply(0.99, 0.98, 0.99)
        pos = pos.add(vel.x, vel.y, vel.z)

        if pos.y <= 0:
            break

    return pos, vel


# =========================
# BASIC CONTROLLER
# =========================

class BasicController:
    def __init__(self):
        
        '''
        #Fork
        self.height_delta = 40
        self.pitch_up_speed = 5.45
        self.pitch_down_speed = 0.90
        self.down_angle = 37.72
        self.up_angle = -54.77
        '''
        
        '''
        #Legacy
        self.height_delta = 60
        self.pitch_up_speed = 4
        self.pitch_down_speed = 4
        self.down_angle = 40
        self.up_angle = -40
        '''
        

        #Modern
        self.height_delta = 40
        self.pitch_up_speed = 15
        self.pitch_down_speed = 0.5
        self.down_angle = 32
        self.up_angle = -49

        
        
        
        self.pitch = self.down_angle
        self.pitching_down = True
        self.base_height = 0

    def tick(self, y):
        lower = self.base_height
        upper = self.base_height + self.height_delta

        if self.pitching_down and y <= lower:
            self.pitching_down = False
        elif not self.pitching_down and y >= upper:
            self.pitching_down = True

        if not self.pitching_down:
            self.pitch -= self.pitch_up_speed
            if self.pitch < self.up_angle:
                self.pitch = self.up_angle
                self.pitching_down = True
        else:
            if self.pitch < self.down_angle:
                self.pitch += self.pitch_down_speed

        return self.pitch


# =========================
# DATA COLLECTION
# =========================

def collect_data(ticks=30000):
    ctrl = BasicController()

    pos = Vec3(0, ctrl.height_delta, 0)
    vel = Vec3(0, 0, 0)
    prev_pitch = ctrl.pitch

    data = []

    for _ in range(ticks):
        pitch = ctrl.tick(pos.y)

        xSpeed = vel.horizontalDistance()
        ySpeed = vel.y

        data.append((
            pos.y,
            xSpeed,
            ySpeed,
            prev_pitch,
            pitch
        ))

        pos, vel = elytraPointer(
            ticks=1,
            xRot=pitch,
            yRot=-90,
            deltaMovement=vel,
            startingPosition=pos
        )

        prev_pitch = pitch

        if pos.y <= -10:
            break

    return data


# =========================
# VISUALIZATION
# =========================

def visualize(data):
    h = [d[0] for d in data]
    xs = [d[1] for d in data]
    ys = [d[2] for d in data]
    p = [d[4] for d in data]

    plt.figure(figsize=(12, 8))

    plt.subplot(2, 2, 1)
    plt.scatter(h, p, s=2)
    plt.xlabel("Height")
    plt.ylabel("xRot")

    plt.subplot(2, 2, 2)
    plt.scatter(xs, p, s=2)
    plt.xlabel("xSpeed")
    plt.ylabel("xRot")

    plt.subplot(2, 2, 3)
    plt.scatter(ys, p, s=2)
    plt.xlabel("ySpeed")
    plt.ylabel("xRot")

    plt.subplot(2, 2, 4)
    plt.scatter(xs, ys, c=p, s=2)
    plt.xlabel("xSpeed")
    plt.ylabel("ySpeed")
    plt.colorbar(label="xRot")

    plt.tight_layout()
    plt.show()


# =========================
# RUN
# =========================

if __name__ == "__main__":
    data = collect_data()
    visualize(data)

@DortyTheGreat
Copy link
Contributor Author

DortyTheGreat commented Dec 31, 2025

It could be possible to decrease angle not linearly (in order to get even faster), but I have no idea what kind of function to use. Maybe someone can find a better approach, although changing constants might be accepted as a pull request with a higher probability rather than some rewrites

@Wide-Cat Wide-Cat merged commit 466ffa5 into MeteorDevelopment:master Jan 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants