Trying to make a wrap around noise should work in theory but I am not getting good results

This Is what I have been able to come up with.

I want this for a sky texture. because I am using a dome I want the edges to line up.
so what I have tried to do was make the gradient on the x axis go back to where it was at the beginning. This would work quite well if a noise only needed one octave. but the effect of increasing the octaves creates a pattern and makes the corners uneven.

this is the code that I have been using.

#pragma once
#include <math.h>
inline float patRand(int x, int y) {
	float xnum = (float)x * 102385.0f;
	float ynum = (float)y * 128473.0f;
	float dist = (sin(xnum) + sin(ynum)) * 2134800;
	return fmod(dist, 1);
}

#pragma once

#include <cmath>
#include <vector>
#include <iostream>
#include <glm/glm.hpp> // For glm::vec2
#include "randPat.h"
// Utility functions
inline float fade(float t) {
    return t * t * t * (t * (t * 6 - 15) + 10); // Perlin's fade function
}

inline float lerp1(float a, float b, float t) {
    return a + t * (b - a); // Linear interpolation
}

inline float dotGridGradient(int ix, int iy, float x, float y, const std::vector<std::vector<glm::vec2>>& gradients, int gridWidth) {
    // Compute the distance vector
    float dx = x - ix;
    float dy = y - iy;

    // Wrap x index for seamless wrapping along x-axis
    int wrappedIx = ix % gridWidth;

    // Calculate the dot product
    glm::vec2 gradient = gradients[iy][wrappedIx];
    return (dx * gradient.x + dy * gradient.y);
}

// Generate wrapping 2D Perlin noise along x-axis only
inline float perlinNoise2D(float x, float y, int gridWidth, int gridHeight, const std::vector<std::vector<glm::vec2>>& gradients) {
    // Determine grid cell coordinates
    int x0 = static_cast<int>(floor(x)) % gridWidth;
    int y0 = static_cast<int>(floor(y)) % gridHeight;
    int x1 = (x0 + 1) % gridWidth;
    int y1 = std::min(y0 + 1, gridHeight - 1); // No wrapping on y-axis

    // Relative coordinates within the cell
    float tx = x - floor(x);
    float ty = y - floor(y);

    // Smooth the coordinates
    float sx = fade(tx);
    float sy = fade(ty);

    // Interpolate dot products of gradient vectors
    float n0, n1, ix0, ix1, value;

    n0 = dotGridGradient(x0, y0, x, y, gradients, gridWidth);
    n1 = dotGridGradient(x1, y0, x, y, gradients, gridWidth);
    ix0 = lerp1(n0, n1, sx);

    n0 = dotGridGradient(x0, y1, x, y, gradients, gridWidth);
    n1 = dotGridGradient(x1, y1, x, y, gradients, gridWidth);
    ix1 = lerp1(n0, n1, sx);

    value = lerp1(ix0, ix1, sy);
    return value;
}

inline float perlinNoise2D_Octaves(float x, float y, int gridWidth, int gridHeight, const std::vector<std::vector<glm::vec2>>& gradients, int octaves, float persistence) {
    float total = 0.0f;
    float maxAmplitude = 0.0f; // Used for normalization
    float frequency = 1.0f / 10.0f;
    float amplitude = 1.0f;

    for (int i = 0; i < octaves; ++i) {
        // Adjust x, y coordinates by frequency
        float sampleX = x * frequency;        
        float sampleY = y * frequency;

        // Add the current octave's contribution
        total += perlinNoise2D(sampleX, sampleY, gridWidth, gridHeight, gradients) * amplitude;

        // Track max amplitude for normalization
        maxAmplitude += amplitude;

        // Update frequency and amplitude for next octave
        frequency *= 1.45f; // Non-linear increase in frequency
        amplitude *= persistence * 1.25f;
    }

    return (total / maxAmplitude) * 2; // Normalize to range [-1, 1]
}

inline float domainWarping(float x, float y, int gridWidth, int gridHeight, const std::vector<std::vector<glm::vec2>>& gradients) {
    float warpX = perlinNoise2D(x * 0.5f, y * 0.5f, gridWidth, gridHeight, gradients) * 2.0f;
    float warpY = perlinNoise2D(x * 0.5f, y * 0.5f, gridWidth, gridHeight, gradients) * 2.0f;

    return perlinNoise2D(x + warpX, y + warpY, gridWidth, gridHeight, gradients);
}

// Initialize gradients for the grid
inline std::vector<std::vector<glm::vec2>> generateGradients(int gridWidth, int gridHeight) {
    std::vector<std::vector<glm::vec2>> gradients(gridHeight, std::vector<glm::vec2>(gridWidth));
    for (int y = 0; y < gridHeight; ++y) {
        for (int x = 0; x < gridWidth; ++x) {
            float angle = patRand(x, y) * 2.0f * 3.1415926535;
            gradients[y][x] = glm::vec2(cos(angle), sin(angle));
        }
    }
    return gradients;
}

#include "CloudTexture.h"

CloudTexture::CloudTexture()
{
}

CloudTexture::~CloudTexture()
{
}

void CloudTexture::init()
{

	unsigned char* imageData = new unsigned char[1048576];

	int octaves = 12;    // Number of layers
	float persistence = 0.5f;
	int gridSize = 16; // Wraps every 8 units
	auto gradients = generateGradients(gridSize, gridSize);

	// Sample the noise function over a grid
	int resolution = 512; // Number of points to evaluate
	for (int j = 0; j < resolution; ++j) {
		for (int i = 0; i < resolution; ++i) {
			float x = static_cast<float>(i) / resolution * gridSize;
			float y = static_cast<float>(j) / resolution * gridSize;
			
			float val = perlinNoise2D_Octaves(x, y, gridSize, gridSize, gradients, octaves, persistence);
			int index = (i + j * 512) * 4;
			int res = (int)(((val + 1.0f) * .5f) * 255);
			if (res < 0) res = 0;
			if (res > 255) res = 255;
			imageData[index] = (unsigned char)res;
			imageData[index + 1] = (unsigned char)res;
			imageData[index + 2] = 255;
			imageData[index + 3] = 255;
		}		
	}

	


	glGenTextures(1, &mTexture);
	glBindTexture(GL_TEXTURE_2D, mTexture); // all upcoming GL_TEXTURE_2D operations will affect our texture object (mTexture)

	// Set the texture wrapping/filtering options (on the currently bound texture object)
	// GL_CLAMP_TO_EDGE
	// GL_REPEAT
	// GL_MIRRORED_REPEAT
	// GL_CLAMP_TO_BORDER
	// GL_LINEAR
	// GL_NEAREST
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

	glGenerateMipmap(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, 0);

	delete imageData;
}

void CloudTexture::update(float delta)
{
	time += delta;
	if (time >= 1) {
		time = 0;
		addunit += 10;
		init();
	}
}

void CloudTexture::bind(GLuint texUnit)
{
	glActiveTexture(GL_TEXTURE0 + texUnit);
	glBindTexture(GL_TEXTURE_2D, mTexture);
}

void CloudTexture::unbind(GLuint texUnit)
{
	glActiveTexture(GL_TEXTURE0 + texUnit);
	glBindTexture(GL_TEXTURE_2D, 0);
}

the update function would be used to make the pattern shift. I am not using it here.

That patRand() function really isn’t very good. Ad-hoc attempts at producing randomness with floating-point functions generally aren’t.

I’d suggest implementing a tried and tested hash function (e.g. CRC32) or RNG (the example rand() implementation from the ISO C standard is fine) using integer arithmetic. Use it to populate an image using imageStore (rendering a quad works for a hash, but not for an RNG).

The thing is I can use a random number function and still get the same results.