11 Commits
png ... amazing

Author SHA1 Message Date
9248bdc362 mega epoch 2026-03-27 11:11:25 -07:00
696a148786 change learning rate 2026-03-27 11:02:59 -07:00
9f278f3269 prep for camera 2026-03-27 11:02:36 -07:00
ee3e173256 add glam dep 2026-03-27 10:54:45 -07:00
d908febda0 normalize and invert depth 2026-03-27 10:52:32 -07:00
4dd494aff1 use const 2026-03-27 10:33:39 -07:00
aa9d7eaace format 2026-03-27 10:33:39 -07:00
f406f126ee adam optimizer 2026-03-27 10:24:52 -07:00
ec73f62f89 drastically reduce hidden layers 2026-03-27 10:23:25 -07:00
25bee24e4c graphics bodge 2 2026-03-27 10:07:01 -07:00
93c01910cb include world offset 2026-03-27 09:54:15 -07:00
3 changed files with 45 additions and 70 deletions

10
Cargo.lock generated
View File

@@ -5451,7 +5451,7 @@ name = "strafe-ai"
version = "0.1.0"
dependencies = [
"burn",
"png",
"glam",
"pollster",
"strafesnet_common",
"strafesnet_graphics",
@@ -5479,9 +5479,9 @@ dependencies = [
[[package]]
name = "strafesnet_graphics"
version = "0.0.11-depth"
version = "0.0.11-depth2"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "16266ca7e57ce802b7abd24c6cd8f9b8d95752f7eaead27e42b431b9768d6135"
checksum = "829804ab9c167365e576de8ebd8a245ad979cb24558b086e693e840697d7956c"
dependencies = [
"bytemuck",
"ddsfile",
@@ -5516,9 +5516,9 @@ dependencies = [
[[package]]
name = "strafesnet_roblox_bot_player"
version = "0.6.2-depth"
version = "0.6.2-depth2"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "12d1aa21c174f23f7f7ede583292a8c82e4b3c483fb0d950e58f84d52807f6ed"
checksum = "f39e7dfc0cb23e482089dc7eac235ad4b274ccfdb8df7617889a90e64a1e247a"
dependencies = [
"glam",
"strafesnet_common",

View File

@@ -8,10 +8,10 @@ burn = { version = "0.20.1", features = ["cuda", "autodiff"] }
wgpu = "29.0.0"
strafesnet_common = { version = "0.9.0", registry = "strafesnet" }
strafesnet_graphics = { version = "=0.0.11-depth", registry = "strafesnet" }
strafesnet_graphics = { version = "=0.0.11-depth2", registry = "strafesnet" }
strafesnet_physics = { version = "=0.0.2-surf", registry = "strafesnet" }
strafesnet_roblox_bot_file = { version = "0.9.4", registry = "strafesnet" }
strafesnet_roblox_bot_player = { version = "=0.6.2-depth", registry = "strafesnet" }
strafesnet_roblox_bot_player = { version = "=0.6.2-depth2", registry = "strafesnet" }
strafesnet_snf = { version = "0.4.0", registry = "strafesnet" }
pollster = "0.4.0"
png = "0.18.1"
glam = "0.32.1"

View File

@@ -1,7 +1,7 @@
use burn::backend::Autodiff;
use burn::nn::loss::{MseLoss, Reduction};
use burn::nn::{Linear, LinearConfig, Relu};
use burn::optim::{GradientsParams, Optimizer, SgdConfig};
use burn::optim::{AdamConfig, GradientsParams, Optimizer};
use burn::prelude::*;
type InferenceBackend = burn::backend::Cuda<f32>;
@@ -11,33 +11,10 @@ const LIMITS: wgpu::Limits = wgpu::Limits::defaults();
use strafesnet_graphics::setup;
use strafesnet_roblox_bot_file::v0;
pub fn output_image_native(image_data: &[u8], texture_dims: (usize, usize), path: String) {
use std::io::Write;
let mut png_data = Vec::<u8>::with_capacity(image_data.len());
let mut encoder =
png::Encoder::new(&mut png_data, texture_dims.0 as u32, texture_dims.1 as u32);
encoder.set_color(png::ColorType::Grayscale);
let mut png_writer = encoder.write_header().unwrap();
png_writer.write_image_data(image_data).unwrap();
png_writer.finish().unwrap();
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(&png_data).unwrap();
}
const SIZE_X: usize = 64;
const SIZE_Y: usize = 36;
const INPUT: usize = SIZE_X * SIZE_Y;
const HIDDEN: [usize; 7] = [
INPUT >> 1,
INPUT >> 2,
INPUT >> 3,
INPUT >> 4,
INPUT >> 5,
INPUT >> 6,
INPUT >> 7,
];
const HIDDEN: [usize; 2] = [INPUT >> 3, INPUT >> 7];
// MoveForward
// MoveLeft
// MoveBack
@@ -104,6 +81,9 @@ fn training() {
.unwrap();
let timelines =
strafesnet_roblox_bot_file::v0::read_all_to_block(std::io::Cursor::new(bot_file)).unwrap();
let bot = strafesnet_roblox_bot_player::bot::CompleteBot::new(timelines).unwrap();
let world_offset = bot.world_offset();
let timelines = bot.timelines();
// setup graphics
let desc = wgpu::InstanceDescriptor::new_without_display_handle_from_env();
@@ -144,9 +124,7 @@ fn training() {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let graphics_texture_view = graphics_texture.create_view(&wgpu::TextureViewDescriptor {
@@ -168,7 +146,7 @@ fn training() {
// training data
let training_samples = timelines.input_events.len() - 1;
let input_size = (size.x * size.y) as usize * size_of::<f32>();
let input_size = INPUT * size_of::<f32>();
let mut inputs = Vec::with_capacity(input_size * training_samples);
let mut targets = Vec::with_capacity(OUTPUT * training_samples);
@@ -181,7 +159,7 @@ fn training() {
let mut last_mx = first.event.mouse_pos.x;
let mut last_my = first.event.mouse_pos.y;
for (i, input_event) in it.enumerate() {
for input_event in it {
let mouse_dx = input_event.event.mouse_pos.x - last_mx;
let mouse_dy = input_event.event.mouse_pos.y - last_my;
last_mx = input_event.event.mouse_pos.x;
@@ -246,29 +224,27 @@ fn training() {
.unwrap(),
};
fn p(v: v0::Vector3) -> [f32; 3] {
[v.x, v.y, v.z]
fn vec3(v: v0::Vector3) -> glam::Vec3 {
glam::vec3(v.x, v.y, v.z)
}
fn a(a: v0::Vector3) -> [f32; 2] {
[a.y, a.x]
fn angles(a: v0::Vector3) -> glam::Vec2 {
glam::vec2(a.y, a.x)
}
let pos = vec3(output_event.event.position) - world_offset;
let angles = angles(output_event.event.angles);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("wgpu encoder"),
});
// render!
graphics.encode_commands(
&mut encoder,
&graphics_texture_view,
p(output_event.event.position).into(),
a(output_event.event.angles).into(),
);
graphics.encode_commands(&mut encoder, &graphics_texture_view, pos, angles);
// copy the depth texture into ram
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &graphics_texture,
texture: graphics.depth_texture(),
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
@@ -305,44 +281,43 @@ fn training() {
}
output_staging_buffer.unmap();
println!("{texture_data:?}");
// discombolulate stride
for y in 0..size.y {
inputs.extend(
texture_data[(stride_size * y) as usize
..(stride_size * y + size.x * size_of::<f32>() as u32) as usize]
.chunks_exact(4)
.map(|b| b[0] as f32 + b[1] as f32 + b[2] as f32),
.map(|b| f32::from_le_bytes(b.try_into().unwrap())),
)
}
// write a png
output_image_native(
&inputs[i * INPUT..(i + 1) * INPUT]
.iter()
.copied()
.map(|f| (f * 255.0) as u8)
.collect::<Vec<u8>>(),
(SIZE_X, SIZE_Y),
format!("depth_images/{i}.png").into(),
);
texture_data.clear();
}
// normalize inputs
let global_min = *inputs
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
let global_max = *inputs
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
let global_range = global_max - global_min;
println!("Normalizing to range {global_min} - {global_max}");
inputs.iter_mut().for_each(|value| {
*value = 1.0 - (*value - global_min) / global_range;
});
let device = burn::backend::cuda::CudaDevice::new(gpu_id);
let mut model: Net<TrainingBackend> = Net::init(&device);
println!("Training model ({} parameters)", model.num_params());
let mut optim = SgdConfig::new().init();
let mut optim = AdamConfig::new().init();
let inputs = Tensor::from_data(
TensorData::new(
inputs,
Shape::new([training_samples, (size.x * size.y) as usize]),
),
TensorData::new(inputs, Shape::new([training_samples, INPUT])),
&device,
);
let targets = Tensor::from_data(
@@ -350,8 +325,8 @@ fn training() {
&device,
);
const LEARNING_RATE: f64 = 0.5;
const EPOCHS: usize = 10000;
const LEARNING_RATE: f64 = 0.001;
const EPOCHS: usize = 100000;
for epoch in 0..EPOCHS {
let predictions = model.forward(inputs.clone());