-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.c
More file actions
3020 lines (2858 loc) · 133 KB
/
Copy pathapp.c
File metadata and controls
3020 lines (2858 loc) · 133 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#define UNICODE 1
#define _UNICODE 1
#include <windows.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef signed int i32;
/*
* Zig/LLD's Windows-GNU driver roots this symbol even in a no-CRT image.
* The program does not use TLS, but defining the standard loader slot keeps
* the final PE on the normal COFF/x64 link path instead of relying on a
* hand-built GNU PE import image.
*/
DWORD _tls_index = 0;
#define ARRAY_COUNT(a) ((u32)(sizeof(a) / sizeof((a)[0])))
#define MAX_N 32u
#define MAX_CELLS (MAX_N * MAX_N)
#define MAX_NODES (MAX_CELLS * 8u)
#define MAX_ROUTE MAX_NODES
#define WALL_N 1u
#define WALL_E 2u
#define WALL_S 4u
#define WALL_W 8u
#define INF64 (~0ull)
#define PACK_SHIFT 20u
#define LOW_MASK ((1ull << PACK_SHIFT) - 1ull)
#define LEFT_PANEL 530
#define WM_APP_CUDA (WM_APP + 1)
#define ID_PROFILE 100
#define ID_SEED 101
#define ID_GENERATE 102
#define ID_SOLVE 103
#define ID_DIAGONAL 104
#define ID_MULTIRUN 105
#define ID_STATUS 106
#define ID_RESULT 107
#define ID_OFFICIAL_SERIES 108
#define ID_OFFICIAL_MAZE 109
#define ID_OFFICIAL_LOAD 110
#define ID_PARAM_BASE 200
#define PTX_RESOURCE_ID 101
enum ParamIndex {
PARAM_STRAIGHT_V,
PARAM_DIAGONAL_V,
PARAM_TURN_V,
PARAM_ACCEL,
PARAM_BRAKE,
PARAM_JERK,
PARAM_MU_X,
PARAM_MU_Y,
PARAM_SLIP,
PARAM_BATTERY,
PARAM_SAG,
PARAM_BATTERY_R,
PARAM_BATTERY_CAPACITY,
PARAM_MASS,
PARAM_CHASSIS_LENGTH,
PARAM_TRACK,
PARAM_WHEEL_RADIUS,
PARAM_WHEEL_MASS,
PARAM_CG_HEIGHT,
PARAM_MOTOR_R,
PARAM_MOTOR_KT,
PARAM_MOTOR_KE,
PARAM_CURRENT_LIMIT,
PARAM_CONTROLLER_KP,
PARAM_RADIUS,
PARAM_MAX_RUN,
PARAM_DT_US,
PARAM_COUNT
};
typedef struct SolverParams {
u32 n;
u32 cell_mm;
u32 straight_v;
u32 diagonal_v;
u32 turn_v;
u32 accel;
u32 brake;
u32 jerk;
u32 mu_milli;
u32 slip_milli;
u32 battery_mv;
u32 sag_mv_s;
u32 turn_radius_mm;
u32 max_run;
u32 mu_y_milli;
u32 battery_r_mohm;
u32 battery_capacity_mah;
u32 mass_g;
u32 chassis_length_mm;
u32 track_mm;
u32 wheel_radius_mm;
u32 wheel_mass_g;
u32 cg_height_mm;
u32 motor_r_mohm;
u32 motor_kt_mnm_a;
u32 motor_ke_mv_rad;
u32 current_limit_ma;
u32 controller_kp_mv_rad;
u32 dt_us;
} SolverParams;
typedef struct DynamicsParams {
float cell_m;
float straight_v;
float diagonal_v;
float turn_v;
float accel;
float brake;
float jerk;
float mu_x;
float mu_y;
float slip_peak;
float battery_v;
float battery_sag_v_s;
float battery_r;
float battery_capacity_c;
float mass;
float chassis_length;
float track;
float wheel_radius;
float wheel_mass;
float cg_height;
float motor_r;
float motor_kt;
float motor_ke;
float current_limit;
float controller_kp;
float turn_radius;
float wheel_inertia;
float yaw_inertia;
float dt;
u32 max_steps;
} DynamicsParams;
typedef struct DynamicsResult {
float time_s;
float max_slip;
float final_voltage;
float max_lateral;
float final_heading_error;
float final_soc;
float final_position_error;
u32 cone_hits;
u32 steps;
u32 valid;
u32 failure_reason;
u32 failure_action;
} DynamicsResult;
enum DynamicsFailureReason {
DYN_FAIL_NONE,
DYN_FAIL_INVALID_PARAMETERS,
DYN_FAIL_NONFINITE,
DYN_FAIL_STATE_LIMIT,
DYN_FAIL_LATERAL_ENVELOPE,
DYN_FAIL_SLIP_LIMIT,
DYN_FAIL_BATTERY_EMPTY,
DYN_FAIL_TOTAL_STEP_LIMIT,
DYN_FAIL_SEGMENT_STEP_LIMIT,
DYN_FAIL_SEGMENT_HEADING,
DYN_FAIL_FINAL_POSITION,
DYN_FAIL_FINAL_HEADING,
DYN_FAIL_COUNT
};
typedef struct GpuCandidateResult {
float score_time_s;
float predicted_time_s;
float max_slip;
float final_voltage;
float max_cone_ratio;
float straight_scale;
float turn_scale;
float final_soc;
u32 cone_hits;
u32 valid;
} GpuCandidateResult;
_Static_assert(sizeof(DynamicsParams) == 120u, "CUDA DynamicsParams ABI changed");
_Static_assert(__builtin_offsetof(DynamicsParams, battery_v) == 40u,
"CUDA battery offset changed");
_Static_assert(__builtin_offsetof(DynamicsParams, mass) == 56u,
"CUDA mass offset changed");
_Static_assert(__builtin_offsetof(DynamicsParams, turn_radius) == 100u,
"CUDA radius offset changed");
_Static_assert(sizeof(GpuCandidateResult) == 40u, "CUDA result ABI changed");
typedef struct TrajectorySample {
float x_m;
float y_m;
float heading_rad;
float speed_m_s;
float lateral_m_s;
float yaw_rate_rad_s;
float slip;
float voltage;
} TrajectorySample;
#define DYNAMICS_CANDIDATES 4096u
#define MAX_TRAJECTORY_SAMPLES 65536u
typedef struct Maze {
u32 n;
u32 cell_mm;
u64 seed;
u8 walls[MAX_CELLS];
u32 goal_entrance_cell;
u32 goal_entrance_dir;
u8 goal_x0, goal_y0, goal_x1, goal_y1;
u8 require_single_goal_entrance;
u8 require_full_connectivity;
u8 require_peg_walls;
u8 require_wall_follower_trap;
} Maze;
#include "official_maze_data.h"
typedef int CUresult;
typedef int CUdevice;
typedef void *CUcontext;
typedef void *CUmodule;
typedef void *CUfunction;
typedef u64 CUdeviceptr;
typedef CUresult (WINAPI *PFN_cuInit)(u32);
typedef CUresult (WINAPI *PFN_cuDriverGetVersion)(int *);
typedef CUresult (WINAPI *PFN_cuDeviceGetCount)(int *);
typedef CUresult (WINAPI *PFN_cuDeviceGet)(CUdevice *, int);
typedef CUresult (WINAPI *PFN_cuDeviceGetName)(char *, int, CUdevice);
typedef CUresult (WINAPI *PFN_cuDeviceGetAttribute)(int *, int, CUdevice);
typedef CUresult (WINAPI *PFN_cuDeviceTotalMem)(SIZE_T *, CUdevice);
typedef CUresult (WINAPI *PFN_cuCtxCreate)(CUcontext *, u32, CUdevice);
typedef CUresult (WINAPI *PFN_cuCtxDestroy)(CUcontext);
typedef CUresult (WINAPI *PFN_cuModuleLoadDataEx)(CUmodule *, const void *, u32, void *, void **);
typedef CUresult (WINAPI *PFN_cuModuleUnload)(CUmodule);
typedef CUresult (WINAPI *PFN_cuModuleGetFunction)(CUfunction *, CUmodule, const char *);
typedef CUresult (WINAPI *PFN_cuMemAlloc)(CUdeviceptr *, SIZE_T);
typedef CUresult (WINAPI *PFN_cuMemFree)(CUdeviceptr);
typedef CUresult (WINAPI *PFN_cuMemcpyHtoD)(CUdeviceptr, const void *, SIZE_T);
typedef CUresult (WINAPI *PFN_cuMemcpyDtoH)(void *, CUdeviceptr, SIZE_T);
typedef CUresult (WINAPI *PFN_cuLaunchKernel)(CUfunction, u32, u32, u32,
u32, u32, u32, u32,
void *, void **, void **);
typedef CUresult (WINAPI *PFN_cuCtxSynchronize)(void);
typedef CUresult (WINAPI *PFN_cuGetErrorString)(CUresult, const char **);
typedef struct CudaState {
HMODULE library;
CUcontext context;
CUmodule module;
CUfunction relax;
CUfunction dynamics;
CUfunction fem_clear;
CUfunction fem_tie;
CUfunction fem_force;
CUfunction fem_integrate;
CUdevice device;
int driver_version;
int cc_major;
int cc_minor;
int multiprocessors;
int clock_khz;
SIZE_T total_memory;
int ready;
char gpu_name[128];
WCHAR error[256];
PFN_cuInit cuInit;
PFN_cuDriverGetVersion cuDriverGetVersion;
PFN_cuDeviceGetCount cuDeviceGetCount;
PFN_cuDeviceGet cuDeviceGet;
PFN_cuDeviceGetName cuDeviceGetName;
PFN_cuDeviceGetAttribute cuDeviceGetAttribute;
PFN_cuDeviceTotalMem cuDeviceTotalMem;
PFN_cuCtxCreate cuCtxCreate;
PFN_cuCtxDestroy cuCtxDestroy;
PFN_cuModuleLoadDataEx cuModuleLoadDataEx;
PFN_cuModuleUnload cuModuleUnload;
PFN_cuModuleGetFunction cuModuleGetFunction;
PFN_cuMemAlloc cuMemAlloc;
PFN_cuMemFree cuMemFree;
PFN_cuMemcpyHtoD cuMemcpyHtoD;
PFN_cuMemcpyDtoH cuMemcpyDtoH;
PFN_cuLaunchKernel cuLaunchKernel;
PFN_cuCtxSynchronize cuCtxSynchronize;
PFN_cuGetErrorString cuGetErrorString;
} CudaState;
typedef struct AppState {
HINSTANCE instance;
HWND window;
HWND profile;
HWND seed;
HWND generate;
HWND solve;
HWND diagonal;
HWND multirun;
HWND status;
HWND result;
HWND official_series;
HWND official_maze;
HWND official_load;
HWND param_edits[PARAM_COUNT];
HFONT font;
Maze maze;
SolverParams params;
CudaState cuda;
u32 path_nodes[MAX_ROUTE];
u8 path_actions[MAX_ROUTE];
u32 path_count;
u64 best_time_us;
u32 solve_iterations;
int exact_pass;
int dynamics_pass;
u64 graph_time_us;
u32 dynamics_candidate;
u32 dynamics_candidates;
u32 dynamics_cpu_replays;
u32 dynamics_route_attempts;
u32 dynamics_failure_counts[DYN_FAIL_COUNT];
float best_straight_scale;
float best_turn_scale;
float gpu_predicted_time;
float gpu_predicted_slip;
float gpu_max_cone_ratio;
DynamicsResult dynamics_result;
TrajectorySample *trajectory;
u32 trajectory_count;
u32 trajectory_capacity;
int seed_dirty;
int setting_seed_text;
int official_loaded;
u32 official_index;
} AppState;
static AppState g_app;
static AppState *volatile g_relocation_anchor = &g_app;
static u8 g_visit[MAX_CELLS];
static u16 g_stack[MAX_CELLS];
static u16 g_queue[MAX_CELLS];
static u8 g_follower_visit[MAX_CELLS * 4u];
static u64 g_dist[MAX_NODES];
static u32 g_trace_nodes[MAX_ROUTE];
static u8 g_trace_actions[MAX_ROUTE];
static WCHAR g_text[16384];
static WCHAR g_small[256];
static u8 g_candidate_tested[DYNAMICS_CANDIDATES];
static float g_segment_distance[MAX_ROUTE];
static float g_segment_speed_limit[MAX_ROUTE];
static float g_boundary_speed[MAX_ROUTE + 1u];
void *memset(void *destination, int value, SIZE_T size) {
u8 *d = (u8 *)destination;
for (SIZE_T i = 0; i < size; ++i) d[i] = (u8)value;
return destination;
}
void *memcpy(void *destination, const void *source, SIZE_T size) {
u8 *d = (u8 *)destination;
const u8 *s = (const u8 *)source;
for (SIZE_T i = 0; i < size; ++i) d[i] = s[i];
return destination;
}
static SIZE_T wlen(const WCHAR *text) {
SIZE_T n = 0;
if (!text) return 0;
while (text[n]) ++n;
return n;
}
static void wcopy(WCHAR *destination, SIZE_T capacity, const WCHAR *source) {
SIZE_T i = 0;
if (!capacity) return;
while (source && source[i] && i + 1 < capacity) {
destination[i] = source[i];
++i;
}
destination[i] = 0;
}
static void wcat(WCHAR *destination, SIZE_T capacity, const WCHAR *source) {
SIZE_T at = wlen(destination);
SIZE_T i = 0;
if (at >= capacity) return;
while (source && source[i] && at + 1 < capacity) destination[at++] = source[i++];
destination[at] = 0;
}
static void wcat_char(WCHAR *destination, SIZE_T capacity, WCHAR value) {
SIZE_T at = wlen(destination);
if (at + 1 >= capacity) return;
destination[at] = value;
destination[at + 1] = 0;
}
static void wcat_u64(WCHAR *destination, SIZE_T capacity, u64 value) {
WCHAR digits[32];
u32 count = 0;
if (!value) {
wcat_char(destination, capacity, L'0');
return;
}
while (value && count < ARRAY_COUNT(digits)) {
digits[count++] = (WCHAR)(L'0' + (value % 10ull));
value /= 10ull;
}
while (count) wcat_char(destination, capacity, digits[--count]);
}
static void wcat_fixed6(WCHAR *destination, SIZE_T capacity, u64 microseconds) {
u64 seconds = microseconds / 1000000ull;
u32 fraction = (u32)(microseconds % 1000000ull);
wcat_u64(destination, capacity, seconds);
wcat_char(destination, capacity, L'.');
u32 divisor = 100000u;
while (divisor) {
wcat_char(destination, capacity, (WCHAR)(L'0' + (fraction / divisor) % 10u));
divisor /= 10u;
}
}
static void wcat_float3(WCHAR *destination, SIZE_T capacity, float value) {
if (!(value == value)) {
wcat(destination, capacity, L"NaN");
return;
}
if (value < 0.0f) {
wcat_char(destination, capacity, L'-');
value = -value;
}
if (value > 4294967.0f) {
wcat(destination, capacity, L">4294967");
return;
}
u64 milli = (u64)(value * 1000.0f + 0.5f);
wcat_u64(destination, capacity, milli / 1000ull);
wcat_char(destination, capacity, L'.');
u32 fraction = (u32)(milli % 1000ull);
wcat_char(destination, capacity, (WCHAR)(L'0' + fraction / 100u));
wcat_char(destination, capacity, (WCHAR)(L'0' + (fraction / 10u) % 10u));
wcat_char(destination, capacity, (WCHAR)(L'0' + fraction % 10u));
}
static void ascii_to_wide(WCHAR *destination, SIZE_T capacity, const char *source) {
SIZE_T i = 0;
if (!capacity) return;
while (source && source[i] && i + 1 < capacity) {
unsigned char c = (unsigned char)source[i];
destination[i] = c < 128 ? (WCHAR)c : L'?';
++i;
}
destination[i] = 0;
}
static u32 parse_u32_text(const WCHAR *text, u32 fallback, u32 minimum, u32 maximum) {
u64 value = 0;
int found = 0;
for (u32 i = 0; text && text[i]; ++i) {
if (text[i] >= L'0' && text[i] <= L'9') {
found = 1;
value = value * 10ull + (u32)(text[i] - L'0');
if (value > maximum) return maximum;
} else if (text[i] == L' ' || text[i] == L'\t') {
continue;
} else {
break;
}
}
if (!found) return fallback;
if (value < minimum) return minimum;
return (u32)value;
}
static u32 parse_milli_text(const WCHAR *text, u32 fallback, u32 minimum, u32 maximum) {
u64 whole = 0;
u32 fraction = 0;
u32 fraction_digits = 0;
int decimal = 0;
int found = 0;
for (u32 i = 0; text && text[i]; ++i) {
WCHAR c = text[i];
if (c >= L'0' && c <= L'9') {
found = 1;
if (!decimal) whole = whole * 10ull + (u32)(c - L'0');
else if (fraction_digits < 3u) {
fraction = fraction * 10u + (u32)(c - L'0');
++fraction_digits;
}
} else if ((c == L'.' || c == L',') && !decimal) {
decimal = 1;
} else if (c == L' ' || c == L'\t') {
continue;
} else {
break;
}
}
if (!found) return fallback;
while (fraction_digits < 3u) {
fraction *= 10u;
++fraction_digits;
}
u64 result = whole * 1000ull + fraction;
if (result < minimum) result = minimum;
if (result > maximum) result = maximum;
return (u32)result;
}
static u32 get_edit_u32(HWND edit, u32 fallback, u32 minimum, u32 maximum) {
WCHAR buffer[64];
GetWindowTextW(edit, buffer, (int)ARRAY_COUNT(buffer));
return parse_u32_text(buffer, fallback, minimum, maximum);
}
static u32 get_edit_milli(HWND edit, u32 fallback, u32 minimum, u32 maximum) {
WCHAR buffer[64];
GetWindowTextW(edit, buffer, (int)ARRAY_COUNT(buffer));
return parse_milli_text(buffer, fallback, minimum, maximum);
}
static u64 rng_next(u64 *state) {
u64 x = *state;
if (!x) x = 0x9e3779b97f4a7c15ull;
x ^= x >> 12u;
x ^= x << 25u;
x ^= x >> 27u;
*state = x;
return x * 2685821657736338717ull;
}
static u32 next_seed_u32(u32 current) {
u64 state = (u64)current ^ 0xd1b54a32d192ed03ull;
u64 mixed = rng_next(&state);
u32 next = (u32)(mixed ^ (mixed >> 32u));
if (!next || next == current) next ^= 0xa511e9b3u;
return next ? next : 1u;
}
static void set_seed_text(u32 seed) {
g_small[0] = 0;
wcat_u64(g_small, ARRAY_COUNT(g_small), seed);
g_app.setting_seed_text = 1;
SetWindowTextW(g_app.seed, g_small);
g_app.setting_seed_text = 0;
}
static int is_goal_cell(const Maze *maze, u32 cell) {
u32 x = cell % maze->n;
u32 y = cell / maze->n;
return x >= maze->goal_x0 && x <= maze->goal_x1 &&
y >= maze->goal_y0 && y <= maze->goal_y1;
}
static const i32 g_dx4[4] = {0, 1, 0, -1};
static const i32 g_dy4[4] = {1, 0, -1, 0};
static const u8 g_bit4[4] = {WALL_N, WALL_E, WALL_S, WALL_W};
static const u8 g_opp4[4] = {WALL_S, WALL_W, WALL_N, WALL_E};
static void carve_edge(Maze *maze, u32 cell, u32 direction) {
i32 x = (i32)(cell % maze->n);
i32 y = (i32)(cell / maze->n);
i32 nx = x + g_dx4[direction];
i32 ny = y + g_dy4[direction];
if (nx < 0 || ny < 0 || nx >= (i32)maze->n || ny >= (i32)maze->n) return;
u32 neighbor = (u32)ny * maze->n + (u32)nx;
maze->walls[cell] &= (u8)~g_bit4[direction];
maze->walls[neighbor] &= (u8)~g_opp4[direction];
}
static void add_edge(Maze *maze, u32 cell, u32 direction) {
i32 x = (i32)(cell % maze->n);
i32 y = (i32)(cell / maze->n);
i32 nx = x + g_dx4[direction];
i32 ny = y + g_dy4[direction];
if (nx < 0 || ny < 0 || nx >= (i32)maze->n || ny >= (i32)maze->n) return;
u32 neighbor = (u32)ny * maze->n + (u32)nx;
maze->walls[cell] |= g_bit4[direction];
maze->walls[neighbor] |= g_opp4[direction];
}
static int is_start_allowed_edge(u32 n, u32 a, u32 b) {
if (a != 0u && b != 0u) return 1;
return (a == 0u && b == n) || (b == 0u && a == n);
}
static int maze_is_fully_connected(const Maze *maze) {
u32 cells = maze->n * maze->n;
memset(g_visit, 0, cells);
u32 head = 0u, tail = 0u;
g_queue[tail++] = 0u;
g_visit[0] = 1u;
while (head < tail) {
u32 cell = g_queue[head++];
u32 x = cell % maze->n;
u32 y = cell / maze->n;
for (u32 direction = 0; direction < 4u; ++direction) {
if (maze->walls[cell] & g_bit4[direction]) continue;
i32 nx = (i32)x + g_dx4[direction];
i32 ny = (i32)y + g_dy4[direction];
if (nx < 0 || ny < 0 || nx >= (i32)maze->n || ny >= (i32)maze->n)
return 0;
u32 next = (u32)ny * maze->n + (u32)nx;
if (!g_visit[next]) {
g_visit[next] = 1u;
g_queue[tail++] = (u16)next;
}
}
}
return tail == cells;
}
static int interior_post_has_wall(const Maze *maze, u32 px, u32 py) {
u32 n = maze->n;
if (!px || !py || px >= n || py >= n) return 1;
if (maze->walls[(py - 1u) * n + (px - 1u)] & WALL_N) return 1;
if (maze->walls[(py - 1u) * n + px] & WALL_N) return 1;
if (maze->walls[(py - 1u) * n + (px - 1u)] & WALL_E) return 1;
if (maze->walls[py * n + (px - 1u)] & WALL_E) return 1;
return 0;
}
static int wall_follower_reaches_goal(const Maze *maze, int keep_left) {
u32 cells = maze->n * maze->n;
memset(g_follower_visit, 0, cells * 4u);
u32 cell = 0u;
u32 heading = 0u;
static const i32 left_order[4] = {-1, 0, 1, 2};
static const i32 right_order[4] = {1, 0, -1, 2};
const i32 *order = keep_left ? left_order : right_order;
for (u32 step = 0; step <= cells * 4u; ++step) {
u32 state = cell * 4u + heading;
if (g_follower_visit[state]) return 0;
g_follower_visit[state] = 1u;
u32 direction = heading;
int found = 0;
for (u32 choice = 0; choice < 4u; ++choice) {
direction = (u32)((i32)heading + order[choice] + 4) & 3u;
if (!(maze->walls[cell] & g_bit4[direction])) { found = 1; break; }
}
if (!found) return 0;
i32 x = (i32)(cell % maze->n) + g_dx4[direction];
i32 y = (i32)(cell / maze->n) + g_dy4[direction];
if (x < 0 || y < 0 || x >= (i32)maze->n || y >= (i32)maze->n) return 0;
cell = (u32)y * maze->n + (u32)x;
heading = direction;
if (is_goal_cell(maze, cell)) return 1;
}
return 0;
}
static int validate_maze(const Maze *maze) {
u32 n = maze->n;
if (n < 2u || n > MAX_N ||
maze->goal_x0 > maze->goal_x1 || maze->goal_y0 > maze->goal_y1 ||
maze->goal_x1 >= n || maze->goal_y1 >= n) return 0;
for (u32 y = 0; y < n; ++y) {
for (u32 x = 0; x < n; ++x) {
u32 c = y * n + x;
u8 w = maze->walls[c];
if (y == n - 1u && !(w & WALL_N)) return 0;
if (x == n - 1u && !(w & WALL_E)) return 0;
if (y == 0u && !(w & WALL_S)) return 0;
if (x == 0u && !(w & WALL_W)) return 0;
if (y + 1u < n && ((w & WALL_N) != 0) != ((maze->walls[c + n] & WALL_S) != 0)) return 0;
if (x + 1u < n && ((w & WALL_E) != 0) != ((maze->walls[c + 1u] & WALL_W) != 0)) return 0;
}
}
if ((maze->walls[0] & (WALL_E | WALL_S | WALL_W)) != (WALL_E | WALL_S | WALL_W)) return 0;
if (maze->walls[0] & WALL_N) return 0;
u32 goal_openings = 0;
for (u32 y = maze->goal_y0; y <= maze->goal_y1; ++y) {
for (u32 x = maze->goal_x0; x <= maze->goal_x1; ++x) {
u32 c = y * n + x;
if (x < maze->goal_x1 && (maze->walls[c] & WALL_E)) return 0;
if (y < maze->goal_y1 && (maze->walls[c] & WALL_N)) return 0;
if (x == maze->goal_x0 && !(maze->walls[c] & WALL_W)) ++goal_openings;
if (x == maze->goal_x1 && !(maze->walls[c] & WALL_E)) ++goal_openings;
if (y == maze->goal_y0 && !(maze->walls[c] & WALL_S)) ++goal_openings;
if (y == maze->goal_y1 && !(maze->walls[c] & WALL_N)) ++goal_openings;
}
}
if (!goal_openings || (maze->require_single_goal_entrance && goal_openings != 1u)) return 0;
if (!maze_is_fully_connected(maze) && maze->require_full_connectivity) return 0;
int goal_reached = 0;
for (u32 y = maze->goal_y0; y <= maze->goal_y1; ++y) {
for (u32 x = maze->goal_x0; x <= maze->goal_x1; ++x) {
if (g_visit[y * n + x]) goal_reached = 1;
}
}
if (!goal_reached) return 0;
if (maze->require_peg_walls) {
for (u32 py = 1u; py < n; ++py) {
for (u32 px = 1u; px < n; ++px) {
if (px == n / 2u && py == n / 2u) {
if (interior_post_has_wall(maze, px, py)) return 0;
} else if (!interior_post_has_wall(maze, px, py)) return 0;
}
}
}
if (maze->require_wall_follower_trap &&
(wall_follower_reaches_goal(maze, 1) || wall_follower_reaches_goal(maze, 0)))
return 0;
return 1;
}
static u32 official_maze_hash(const Maze *maze) {
u32 value = 2166136261u;
u8 prefix[7] = {
(u8)maze->n, (u8)(maze->cell_mm & 255u), (u8)(maze->cell_mm >> 8u),
maze->goal_x0, maze->goal_y0, maze->goal_x1, maze->goal_y1
};
for (u32 i = 0; i < ARRAY_COUNT(prefix); ++i)
value = (value ^ prefix[i]) * 16777619u;
for (u32 i = 0; i < maze->n * maze->n; ++i)
value = (value ^ maze->walls[i]) * 16777619u;
return value;
}
static int decode_official_maze(Maze *maze, u32 index) {
if (index >= OFFICIAL_MAZE_COUNT) return 0;
const OfficialMazeRecord *record = &g_official_mazes[index];
u32 cells = (u32)record->n * record->n;
if (record->n < 2u || record->n > MAX_N ||
record->data_offset > ARRAY_COUNT(g_official_maze_data) ||
cells > ARRAY_COUNT(g_official_maze_data) - record->data_offset) return 0;
memset(maze, 0, sizeof(*maze));
maze->n = record->n;
maze->cell_mm = record->cell_mm;
maze->goal_x0 = record->goal_x0;
maze->goal_y0 = record->goal_y0;
maze->goal_x1 = record->goal_x1;
maze->goal_y1 = record->goal_y1;
maze->require_single_goal_entrance = 0u;
maze->require_full_connectivity = 0u;
memcpy(maze->walls, g_official_maze_data + record->data_offset, cells);
return official_maze_hash(maze) == record->data_hash && validate_maze(maze);
}
static int repair_empty_posts(Maze *maze, u64 *random_state) {
u32 n = maze->n;
for (u32 py = 1u; py < n; ++py) {
for (u32 px = 1u; px < n; ++px) {
if ((px == n / 2u && py == n / 2u) ||
interior_post_has_wall(maze, px, py)) continue;
u32 cells[4] = {
(py - 1u) * n + (px - 1u),
(py - 1u) * n + px,
(py - 1u) * n + (px - 1u),
py * n + (px - 1u)
};
u32 directions[4] = {0u, 0u, 1u, 1u};
u32 start = (u32)(rng_next(random_state) & 3ull);
int repaired = 0;
for (u32 offset = 0; offset < 4u; ++offset) {
u32 pick = (start + offset) & 3u;
u32 cell = cells[pick];
u32 direction = directions[pick];
i32 nx = (i32)(cell % n) + g_dx4[direction];
i32 ny = (i32)(cell / n) + g_dy4[direction];
if (nx < 0 || ny < 0 || nx >= (i32)n || ny >= (i32)n) continue;
u32 neighbor = (u32)ny * n + (u32)nx;
if (is_goal_cell(maze, cell) || is_goal_cell(maze, neighbor) ||
!is_start_allowed_edge(n, cell, neighbor) ||
(maze->walls[cell] & g_bit4[direction])) continue;
add_edge(maze, cell, direction);
if (maze_is_fully_connected(maze)) {
repaired = 1;
break;
}
carve_edge(maze, cell, direction);
}
if (!repaired) return 0;
}
}
return 1;
}
static int generate_maze_attempt(Maze *maze, u32 n, u32 cell_mm,
u64 display_seed, u64 working_seed) {
if (n != 16u && n != 32u) return 0;
maze->n = n;
maze->cell_mm = cell_mm;
maze->seed = display_seed;
maze->goal_x0 = maze->goal_y0 = (u8)(n / 2u - 1u);
maze->goal_x1 = maze->goal_y1 = (u8)(n / 2u);
maze->require_single_goal_entrance = 1u;
maze->require_full_connectivity = 1u;
maze->require_peg_walls = 1u;
maze->require_wall_follower_trap = 1u;
u32 cells = n * n;
for (u32 i = 0; i < cells; ++i) maze->walls[i] = 0x0fu;
memset(g_visit, 0, cells);
u64 random_state = working_seed ? working_seed : 1ull;
u32 stack_size = 1u;
g_stack[0] = 0u;
g_visit[0] = 1u;
while (stack_size) {
u32 current = g_stack[stack_size - 1u];
u32 cx = current % n;
u32 cy = current / n;
u32 candidates[4];
u32 directions[4];
u32 count = 0;
for (u32 d = 0; d < 4u; ++d) {
i32 nx = (i32)cx + g_dx4[d];
i32 ny = (i32)cy + g_dy4[d];
if (nx < 0 || ny < 0 || nx >= (i32)n || ny >= (i32)n) continue;
u32 next = (u32)ny * n + (u32)nx;
if (is_goal_cell(maze, next) || g_visit[next]) continue;
if (!is_start_allowed_edge(n, current, next)) continue;
candidates[count] = next;
directions[count] = d;
++count;
}
if (!count) {
--stack_size;
continue;
}
u32 choice = (u32)(rng_next(&random_state) % count);
u32 next = candidates[choice];
carve_edge(maze, current, directions[choice]);
g_visit[next] = 1u;
g_stack[stack_size++] = (u16)next;
}
for (u32 i = 0; i < cells; ++i) {
if (!is_goal_cell(maze, i) && !g_visit[i]) return 0;
}
u32 g0 = n / 2u - 1u;
u32 g1 = n / 2u;
carve_edge(maze, g0 * n + g0, 1u);
carve_edge(maze, g0 * n + g0, 0u);
carve_edge(maze, g0 * n + g1, 0u);
carve_edge(maze, g1 * n + g0, 1u);
u32 perimeter_cells[8] = {
g0 * n + g0, g1 * n + g0,
g0 * n + g1, g1 * n + g1,
g0 * n + g0, g0 * n + g1,
g1 * n + g0, g1 * n + g1
};
u32 perimeter_dirs[8] = {3u, 3u, 1u, 1u, 2u, 2u, 0u, 0u};
u32 entrance = (u32)(rng_next(&random_state) & 7ull);
maze->goal_entrance_cell = perimeter_cells[entrance];
maze->goal_entrance_dir = perimeter_dirs[entrance];
carve_edge(maze, maze->goal_entrance_cell, maze->goal_entrance_dir);
for (u32 y = 0; y < n; ++y) {
for (u32 x = 0; x < n; ++x) {
u32 c = y * n + x;
if (is_goal_cell(maze, c)) continue;
for (u32 d = 0; d < 2u; ++d) {
i32 nx = (i32)x + g_dx4[d];
i32 ny = (i32)y + g_dy4[d];
if (nx < 0 || ny < 0 || nx >= (i32)n || ny >= (i32)n) continue;
u32 next = (u32)ny * n + (u32)nx;
if (is_goal_cell(maze, next)) continue;
if (!is_start_allowed_edge(n, c, next)) continue;
if ((maze->walls[c] & g_bit4[d]) && (rng_next(&random_state) % 100ull) < 14ull)
carve_edge(maze, c, d);
}
}
}
if (!repair_empty_posts(maze, &random_state)) return 0;
return validate_maze(maze);
}
static int generate_maze(Maze *maze, u32 n, u32 cell_mm, u64 seed) {
u64 base = seed ? seed : 1ull;
for (u32 attempt = 0; attempt < 512u; ++attempt) {
u64 working = base ^ (0x9e3779b97f4a7c15ull * (u64)(attempt + 1u));
if (generate_maze_attempt(maze, n, cell_mm, base, working)) return 1;
}
return 0;
}
static u64 isqrt_u64(u64 value) {
u64 result = 0;
u64 bit = 1ull << 62u;
while (bit > value) bit >>= 2u;
while (bit) {
if (value >= result + bit) {
value -= result + bit;
result = (result >> 1u) + bit;
} else {
result >>= 1u;
}
bit >>= 2u;
}
return result;
}
static void read_solver_params(SolverParams *p) {
int profile = (int)SendMessageW(g_app.profile, CB_GETCURSEL, 0, 0);
if (g_app.official_loaded) {
p->n = g_app.maze.n;
p->cell_mm = g_app.maze.cell_mm;
} else {
p->n = profile == 1 ? 32u : 16u;
p->cell_mm = profile == 1 ? 90u : 180u;
}
p->straight_v = get_edit_u32(g_app.param_edits[PARAM_STRAIGHT_V], 3500u, 100u, 20000u);
p->diagonal_v = get_edit_u32(g_app.param_edits[PARAM_DIAGONAL_V], 3000u, 100u, 20000u);
p->turn_v = get_edit_u32(g_app.param_edits[PARAM_TURN_V], 1800u, 50u, 10000u);
p->accel = get_edit_u32(g_app.param_edits[PARAM_ACCEL], 12000u, 100u, 100000u);
p->brake = get_edit_u32(g_app.param_edits[PARAM_BRAKE], 14000u, 100u, 100000u);
p->jerk = get_edit_u32(g_app.param_edits[PARAM_JERK], 120000u, 1000u, 2000000u);
p->mu_milli = get_edit_milli(g_app.param_edits[PARAM_MU_X], 900u, 100u, 3000u);
p->mu_y_milli = get_edit_milli(g_app.param_edits[PARAM_MU_Y], 1050u, 100u, 3000u);
p->slip_milli = get_edit_milli(g_app.param_edits[PARAM_SLIP], 80u, 0u, 2000u);
p->battery_mv = get_edit_u32(g_app.param_edits[PARAM_BATTERY], 8400u, 3000u, 30000u);
p->sag_mv_s = get_edit_u32(g_app.param_edits[PARAM_SAG], 90u, 0u, 5000u);
p->battery_r_mohm = get_edit_u32(g_app.param_edits[PARAM_BATTERY_R], 45u, 0u, 2000u);
p->battery_capacity_mah = get_edit_u32(g_app.param_edits[PARAM_BATTERY_CAPACITY], 350u, 10u, 20000u);
p->mass_g = get_edit_u32(g_app.param_edits[PARAM_MASS], 100u, 10u, 2000u);
p->chassis_length_mm = get_edit_u32(g_app.param_edits[PARAM_CHASSIS_LENGTH], 90u, 20u, 300u);
p->track_mm = get_edit_u32(g_app.param_edits[PARAM_TRACK], 72u, 10u, 200u);
p->wheel_radius_mm = get_edit_u32(g_app.param_edits[PARAM_WHEEL_RADIUS], 12u, 2u, 100u);
p->wheel_mass_g = get_edit_u32(g_app.param_edits[PARAM_WHEEL_MASS], 5u, 1u, 100u);
p->cg_height_mm = get_edit_u32(g_app.param_edits[PARAM_CG_HEIGHT], 15u, 0u, 100u);
p->motor_r_mohm = get_edit_u32(g_app.param_edits[PARAM_MOTOR_R], 320u, 10u, 5000u);
p->motor_kt_mnm_a = get_edit_u32(g_app.param_edits[PARAM_MOTOR_KT], 18u, 1u, 500u);
p->motor_ke_mv_rad = get_edit_u32(g_app.param_edits[PARAM_MOTOR_KE], 2u, 0u, 100u);
p->current_limit_ma = get_edit_u32(g_app.param_edits[PARAM_CURRENT_LIMIT], 6000u, 100u, 100000u);
p->controller_kp_mv_rad = get_edit_u32(g_app.param_edits[PARAM_CONTROLLER_KP], 40u, 1u, 5000u);
p->turn_radius_mm = get_edit_u32(g_app.param_edits[PARAM_RADIUS], 45u, 5u, 500u);
p->max_run = get_edit_u32(g_app.param_edits[PARAM_MAX_RUN], 8u, 1u, 8u);
p->dt_us = get_edit_u32(g_app.param_edits[PARAM_DT_US], 500u, 50u, 5000u);
if (SendMessageW(g_app.multirun, BM_GETCHECK, 0, 0) != BST_CHECKED) p->max_run = 1u;
if (SendMessageW(g_app.diagonal, BM_GETCHECK, 0, 0) != BST_CHECKED) p->diagonal_v = 0u;
u64 lateral_square = ((u64)p->mu_milli * 9810ull * p->turn_radius_mm) / 1000ull;
u32 lateral_limit = (u32)isqrt_u64(lateral_square);
if (lateral_limit && p->turn_v > lateral_limit) p->turn_v = lateral_limit;
}
static int step_ok_host(const Maze *maze, i32 x, i32 y, u32 heading) {
static const i32 dx[8] = {0, 1, 1, 1, 0, -1, -1, -1};
static const i32 dy[8] = {1, 1, 0, -1, -1, -1, 0, 1};
i32 nx = x + dx[heading];
i32 ny = y + dy[heading];
if (nx < 0 || ny < 0 || nx >= (i32)maze->n || ny >= (i32)maze->n) return 0;
u32 c = (u32)y * maze->n + (u32)x;
u8 w = maze->walls[c];
if (!(heading & 1u)) {
u8 bit = heading == 0u ? WALL_N : heading == 2u ? WALL_E : heading == 4u ? WALL_S : WALL_W;
return !(w & bit);
}
if (heading == 1u) return !(w & (WALL_N | WALL_E)) && !(maze->walls[c + 1u] & WALL_N) && !(maze->walls[c + maze->n] & WALL_E);
if (heading == 3u) return !(w & (WALL_S | WALL_E)) && !(maze->walls[c + 1u] & WALL_S) && !(maze->walls[c - maze->n] & WALL_E);
if (heading == 5u) return !(w & (WALL_S | WALL_W)) && !(maze->walls[c - 1u] & WALL_S) && !(maze->walls[c - maze->n] & WALL_W);
return !(w & (WALL_N | WALL_W)) && !(maze->walls[c - 1u] & WALL_N) && !(maze->walls[c + maze->n] & WALL_W);
}
static u32 voltage_scaled_host(u32 value, u64 elapsed_us, const SolverParams *p) {
u64 drop = ((u64)p->sag_mv_s * elapsed_us) / 1000000ull;
u32 floor_voltage = p->battery_mv * 60u / 100u;
u32 voltage = drop >= p->battery_mv ? floor_voltage : p->battery_mv - (u32)drop;
if (voltage < floor_voltage) voltage = floor_voltage;
u64 scaled = (u64)value * voltage / (p->battery_mv ? p->battery_mv : 1u);
return scaled ? (u32)scaled : 1u;
}
static u64 forward_cost_host(u32 heading, u32 steps, u64 elapsed_us, const SolverParams *p) {
u64 distance = (u64)p->cell_mm * steps;
u32 vmax = (heading & 1u) ? p->diagonal_v : p->straight_v;
if (heading & 1u) distance = (distance * 1414ull + 500ull) / 1000ull;
vmax = voltage_scaled_host(vmax, elapsed_us, p);
u32 traction_accel = p->mu_milli * 9810u / 1000u;
u32 accel = voltage_scaled_host(p->accel, elapsed_us, p);
u32 brake = voltage_scaled_host(p->brake, elapsed_us, p);
if (accel > traction_accel) accel = traction_accel;
if (brake > traction_accel) brake = traction_accel;
if (!accel) accel = 1u;
if (!brake) brake = 1u;
u64 cruise = (distance * 1000000ull + vmax - 1u) / vmax;
u64 ramp = (u64)vmax * 500000ull / accel + (u64)vmax * 500000ull / brake;
u32 jerk = p->jerk ? p->jerk : 1u;
u64 jerk_penalty = (u64)(accel + brake) * 500000ull / jerk;
return cruise + ramp + jerk_penalty + 1ull;
}
static u64 turn_cost_host(u32 new_heading, u32 angle_mrad, u64 elapsed_us, const SolverParams *p) {
u64 distance = angle_mrad == 3142u ? 0u : p->cell_mm;
if (new_heading & 1u) distance = (distance * 1414ull + 500ull) / 1000ull;
u32 speed = voltage_scaled_host(p->turn_v, elapsed_us, p);
u64 travel = (distance * 1000000ull + speed - 1u) / speed;
u64 arc = ((u64)p->turn_radius_mm * angle_mrad * 1000ull + speed - 1u) / speed;
arc = (arc * (1000u + p->slip_milli) + 999u) / 1000u;