Skip to content
This repository was archived by the owner on May 31, 2026. It is now read-only.

Commit 508dcc5

Browse files
Paradoxdovclaude
andcommitted
v0.4.23: drop "Смещ" column + clear footer during test
Two cleanups requested in UX review: 1. Drop "Смещ" / "Off" column from the core panel. This was the per-core buffer-offset in MB ("@ 128 МБ"), a developer- debug field that meant nothing to a shop tech and chewed 9 chars of horizontal space. Now those 9 chars go into widening the activity bar so cores look more alive. The offset is still in the log and the JSON report for anyone who needs it. 2. Clear the countdown footer once the test actually starts. Pre-v0.4.23 the "[N/14] Test starts in 2 sec [Enter]=start now ..." line lingered all the way through the test run because the row was only painted by countdown() and never wiped afterwards. The hint was meaningless once the test had started — it just sat there. Now: replaced with a minimal in-test footer "[Q] = прервать прогон · логи пишутся в memforge2.log на USB" that's actually relevant while a test is running. No behaviour change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2e51cca commit 508dcc5

3 files changed

Lines changed: 56 additions & 39 deletions

File tree

MemForge2.src.c

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* MemForge2 v0.4.22 — UEFI memory tester written from scratch.
2+
* MemForge2 v0.4.23 — UEFI memory tester written from scratch.
33
*
44
* Latest release: https://github.com/Paradoxdov/memforge/releases
55
* For per-version changes see git log / GitHub Releases page.
@@ -834,7 +834,7 @@ static void init_splash(CHAR16 *stage) {
834834
cls();
835835
UINTN cy = g_h / 2;
836836
/* Title — large centered line. */
837-
CHAR16 *title = L"MEMFORGE v0.4.22";
837+
CHAR16 *title = L"MEMFORGE v0.4.23";
838838
UINTN tx = (g_w - StrLen(title) * g_char_w) / 2;
839839
gfx_draw_str_color(tx, cy - g_char_h * 2, title, COL_ACCENT_HI);
840840
/* Stage indicator — what we're doing right now. */
@@ -938,7 +938,7 @@ static UINTN g_card_cols = 1;
938938
compute_layout(). */
939939
static int g_show_cards = 1;
940940

941-
/* v0.4.22 — focused cards layout for small screens (g_h < 900).
941+
/* v0.4.23 — focused cards layout for small screens (g_h < 900).
942942
Instead of one full-width row per test (14 rows × ~40 px = 560 px,
943943
which on a 1024×768 screen eats 70% of vertical space and clips the
944944
core panel + footer), we draw:
@@ -1008,7 +1008,7 @@ static void compute_layout(UINTN n_tests) {
10081008
g_card_w = g_inner;
10091009
g_card_row_h = g_compact ? g_char_h : (g_char_h + 16);
10101010

1011-
/* v0.4.22 — focused layout on small screens.
1011+
/* v0.4.23 — focused layout on small screens.
10121012
On g_h<900 the per-test card list eats 60-70% of vertical space
10131013
and clips the core panel / footer (YgrecK field report on 1024×768
10141014
Radeon HD 4350). Replace with: 1-row strip of all test dots +
@@ -1222,9 +1222,9 @@ static void render_header(UINT64 elapsed_ms, UINTN done, UINTN total) {
12221222
UINTN cols = g_text_cols;
12231223
if (cols >= 110) {
12241224
SPrint(buf, sizeof(buf),
1225-
T(L" MEMFORGE v0.4.22 | %ld.%ld ГБ RAM | %s "
1225+
T(L" MEMFORGE v0.4.23 | %ld.%ld ГБ RAM | %s "
12261226
L"| %s | прошло %02d:%02d | осталось ~%02d:%02d | Тесты %d/%d",
1227-
L" MEMFORGE v0.4.22 | %ld.%ld GB RAM | %s "
1227+
L" MEMFORGE v0.4.23 | %ld.%ld GB RAM | %s "
12281228
L"| %s | elapsed %02d:%02d | ETA ~%02d:%02d | Tests %d/%d"),
12291229
ram_gb_x10 / 10, ram_gb_x10 % 10,
12301230
pass_tag,
@@ -1234,25 +1234,25 @@ static void render_header(UINT64 elapsed_ms, UINTN done, UINTN total) {
12341234
(UINT32)done, (UINT32)total);
12351235
} else if (cols >= 90) {
12361236
SPrint(buf, sizeof(buf),
1237-
T(L" MEMFORGE v0.4.22 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d | осталось ~%02d:%02d",
1238-
L" MEMFORGE v0.4.22 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d | ETA ~%02d:%02d"),
1237+
T(L" MEMFORGE v0.4.23 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d | осталось ~%02d:%02d",
1238+
L" MEMFORGE v0.4.23 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d | ETA ~%02d:%02d"),
12391239
ram_gb_x10 / 10, ram_gb_x10 % 10,
12401240
pass_tag,
12411241
err_tag,
12421242
secs / 60, secs % 60,
12431243
eta_secs / 60, eta_secs % 60);
12441244
} else if (cols >= 70) {
12451245
SPrint(buf, sizeof(buf),
1246-
T(L" MEMFORGE v0.4.22 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d",
1247-
L" MEMFORGE v0.4.22 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d"),
1246+
T(L" MEMFORGE v0.4.23 | %ld.%ld ГБ RAM | %s | %s | прошло %02d:%02d",
1247+
L" MEMFORGE v0.4.23 | %ld.%ld GB RAM | %s | %s | elapsed %02d:%02d"),
12481248
ram_gb_x10 / 10, ram_gb_x10 % 10,
12491249
pass_tag,
12501250
err_tag,
12511251
secs / 60, secs % 60);
12521252
} else {
12531253
SPrint(buf, sizeof(buf),
1254-
T(L" MEMFORGE v0.4.22 | %s | %s | прошло %02d:%02d",
1255-
L" MEMFORGE v0.4.22 | %s | %s | elapsed %02d:%02d"),
1254+
T(L" MEMFORGE v0.4.23 | %s | %s | прошло %02d:%02d",
1255+
L" MEMFORGE v0.4.23 | %s | %s | elapsed %02d:%02d"),
12561256
pass_tag,
12571257
err_tag,
12581258
secs / 60, secs % 60);
@@ -1778,7 +1778,7 @@ static int dominant_dimm_idx(void) {
17781778
return best;
17791779
}
17801780

1781-
/* v0.4.22 — detect dual-channel interleave ambiguity.
1781+
/* v0.4.23 — detect dual-channel interleave ambiguity.
17821782
On consumer desktops with dual/quad-channel memory, the iMC interleaves
17831783
addresses between channels at 64-byte (cache-line) granularity. A
17841784
SINGLE bad chip on one stick produces errors that, when mapped through
@@ -1787,7 +1787,7 @@ static int dominant_dimm_idx(void) {
17871787

17881788
Field report from a Habr user (Netac DDR4 kit): same stuck bit
17891789
D[53] was reported 24 times, distributed as A2 (8) + B2 (11) + ? (5).
1790-
Pre-v0.4.22 verdict confidently said "REPLACE: DDR4-B2 (HIGH)" — but
1790+
Pre-v0.4.23 verdict confidently said "REPLACE: DDR4-B2 (HIGH)" — but
17911791
physically it's likely ONE bad chip on one of A2/B2, NOT both.
17921792

17931793
This helper returns the list of DIMM indices that each hold >=25% of
@@ -1833,7 +1833,7 @@ static UINTN distributed_dimm_indices(int *out_idx, UINTN cap) {
18331833
return n;
18341834
}
18351835

1836-
/* v0.4.22 — Approach D: detect whether SMBIOS Type 20 reports REAL
1836+
/* v0.4.23 — Approach D: detect whether SMBIOS Type 20 reports REAL
18371837
cache-line interleave (overlapping address ranges across DIMMs) or
18381838
BLOCK mapping (disjoint ranges, each DIMM owns its own physical
18391839
region). PassMark forum & KIT paper both confirm that even though
@@ -1877,7 +1877,7 @@ static UINT8 type20_max_interleave_depth(void) {
18771877
return m;
18781878
}
18791879

1880-
/* v0.4.22 — Approach A: bit-6 polarity analysis of error addresses.
1880+
/* v0.4.23 — Approach A: bit-6 polarity analysis of error addresses.
18811881
On most Intel/AMD consumer dual-channel desktops with DDR4/DDR5, the
18821882
iMC's channel selector is physical address bit 6 (alternating 64-byte
18831883
cache lines between channels). If all error records share the same
@@ -4845,15 +4845,15 @@ static void amd_thermal_probe(void) {
48454845
}
48464846

48474847
static UINT32 amd_thermal_sample(void) {
4848-
/* v0.4.22 — correct decode per Linux k10temp / FreeBSD amdtemp.c:
4848+
/* v0.4.23 — correct decode per Linux k10temp / FreeBSD amdtemp.c:
48494849
SMN 0x59800 (SMU_THM_TCON_CUR_TMP)
48504850
bits [31:21] raw temperature value (11 bits, mask 0x7FF)
48514851
bit 19 TempRangeSel — when SET, scale is -49°C..+206°C
48524852
(subtract 49°C from the raw decode); when CLEAR
48534853
scale is 0..225°C (no offset).
48544854
temp_c = (raw * 0.125) - (range_sel ? 49 : 0)
48554855

4856-
Pre-v0.4.22 code was missing both the 0x7FF mask AND the bit-19
4856+
Pre-v0.4.23 code was missing both the 0x7FF mask AND the bit-19
48574857
range adjustment, which inflated readings by ~49°C on Ryzen SKUs
48584858
that report on the -49..206 scale (most Renoir/Cezanne/Zen3+
48594859
desktop parts). Field report on Ryzen 5 4500 showed Tctl=93°C at
@@ -6473,7 +6473,7 @@ static test_def_t g_tests[] = {
64736473
};
64746474
#define N_TESTS (sizeof(g_tests) / sizeof(g_tests[0]))
64756475

6476-
/* v0.4.22 — map a kernel enum (KER_*) to its position in g_tests[].
6476+
/* v0.4.23 — map a kernel enum (KER_*) to its position in g_tests[].
64776477
CRITICAL: do NOT index g_tests[] directly by a kernel_id_t value.
64786478
The enum values do not match array positions (e.g., KER_AVX2_SUSTAINED
64796479
= 12 maps to position 0 in g_tests because AVX2 Sustained is the
@@ -6625,7 +6625,7 @@ typedef struct {
66256625
} card_info_t;
66266626
static card_info_t g_cards[N_TESTS];
66276627

6628-
/* v0.4.22 — Forward decls for focused-mode helpers (defined below
6628+
/* v0.4.23 — Forward decls for focused-mode helpers (defined below
66296629
card_paint so they can share the same color-lookup logic). */
66306630
static void card_paint_full(UINTN i);
66316631
static void card_strip_paint(UINTN i);
@@ -6739,7 +6739,7 @@ static void card_paint_full(UINTN i) {
67396739
}
67406740
}
67416741

6742-
/* ---------- Focused-mode card painters (v0.4.22) ---------- */
6742+
/* ---------- Focused-mode card painters (v0.4.23) ---------- */
67436743

67446744
/* Paint the small status dot for test i in the top strip. The strip is
67456745
one row tall and shows N evenly-spaced dots, one per test. The dot
@@ -6822,7 +6822,7 @@ static void card_focused_paint(UINTN i) {
68226822
blt_fill(ix, row3_y, iw, row_h, COL_PANEL);
68236823

68246824
/* Row 1: test name (left) + short description in dim color + index counter (right).
6825-
v0.4.22 — description lets non-expert user know what the test
6825+
v0.4.23 — description lets non-expert user know what the test
68266826
actually checks (TRRespass / March-C- / Butterfly etc. are jargon). */
68276827
say_at_px(ix + 4, row1_y, g_tests[i].name);
68286828
UINTN name_chars = StrLen(g_tests[i].name);
@@ -7107,8 +7107,11 @@ static void core_cols_compute(core_cols_t *c) {
71077107
if (slack >= 9 * cw + pad) { w_freq = 9 * cw; slack -= w_freq + pad; }
71087108
/* Priority 4: Per-core MB/s — 6 chars */
71097109
if (slack >= 6 * cw + pad) { w_mbs = 6 * cw; slack -= w_mbs + pad; }
7110-
/* Priority 5: Address offset (debug-ish) — 9 chars ("@1234 МБ") */
7111-
if (slack >= 9 * cw + pad) { w_addr = 9 * cw; slack -= w_addr + pad; }
7110+
/* v0.4.23 — "Смещ" (buffer-offset for this core's slice) column dropped
7111+
from the main test screen. It was a developer-debug field that nobody
7112+
in the field could interpret; removing it frees ~9 chars to widen the
7113+
activity bar. The offset is still in the log and the JSON. */
7114+
(void)w_addr;
71127115
/* Remaining slack goes into the activity bar so it visually fills the
71137116
row. Cap at 16 cw so the bar doesn't look obnoxious on wide screens. */
71147117
if (slack > 0) {
@@ -7470,8 +7473,8 @@ static void drain_conin(void) {
74707473
}
74717474
}
74727475

7473-
/* v0.4.22 — countdown UX rework.
7474-
Pre-v0.4.22: ESC meant "skip the wait and start the test now" — which
7476+
/* v0.4.23 — countdown UX rework.
7477+
Pre-v0.4.23: ESC meant "skip the wait and start the test now" — which
74757478
completely contradicts the universal "ESC = cancel" convention. Users
74767479
pressed ESC expecting "I don't want this test" and instead launched it.
74777480

@@ -8110,7 +8113,7 @@ static void render_simple_verdict(UINT64 total_ms) {
81108113
UINTN dist_n = distributed_dimm_indices(dist_idx, MAX_DIMMS);
81118114
int is_distributed = (dist_n >= 2);
81128115

8113-
/* v0.4.22 — Approach D + A: classify WHY errors are distributed.
8116+
/* v0.4.23 — Approach D + A: classify WHY errors are distributed.
81148117
type20_overlap = 1 → ranges overlap (real cache-line interleave)
81158118
→ "ONE chip behind two labels"
81168119
type20_overlap = 0, depth ≤ 1 → block mode (disjoint ranges,
@@ -8396,8 +8399,8 @@ static void render_summary(UINT64 total_ms) {
83968399
UINTN hrow = (g_hdr_h / 2 - g_char_h / 2) / g_char_h;
83978400
CHAR16 buf[200];
83988401
SPrint(buf, sizeof(buf),
8399-
T(L" MEMFORGE v0.4.22 ИТОГИ | %d сек | Ядра %d/%d",
8400-
L" MEMFORGE v0.4.22 SUMMARY | %d sec | Cores %d/%d"),
8402+
T(L" MEMFORGE v0.4.23 ИТОГИ | %d сек | Ядра %d/%d",
8403+
L" MEMFORGE v0.4.23 SUMMARY | %d sec | Cores %d/%d"),
84018404
(UINT32)(total_ms / 1000),
84028405
(UINT32)g_n_enabled, (UINT32)g_n_cores);
84038406
say_at_rc(0, hrow, buf);
@@ -8479,7 +8482,7 @@ static void render_summary(UINT64 total_ms) {
84798482
CHAR16 chip[64] = L"";
84808483
if (didx >= 0)
84818484
chip_label_for_bit((UINT32)didx, bp, chip, 64);
8482-
/* v0.4.22 — use SMBIOS Type 17 locator string ("DDR4-B2")
8485+
/* v0.4.23 — use SMBIOS Type 17 locator string ("DDR4-B2")
84838486
instead of array-index-based "DIMM%d" which had nothing
84848487
to do with the physical slot label the user sees. */
84858488
CHAR8 *loc = (didx >= 0 && g_dimms[didx].locator[0])
@@ -10190,7 +10193,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1019010193
}
1019110194
}
1019210195

10193-
log_line(L"=== MemForge2 v0.4.22 init ===");
10196+
log_line(L"=== MemForge2 v0.4.23 init ===");
1019410197
log_line(L"[WATCHDOG] UEFI 5-min watchdog disabled at app entry");
1019510198
/* Show splash IMMEDIATELY so the user sees the program is alive while
1019610199
INI parsing, SMBus probes and SMBIOS walk happen. Without this, the
@@ -10235,7 +10238,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1023510238
if (uefi_call_wrapper(g_gop->QueryMode, 4,
1023610239
g_gop, m, &info_sz, &info) != EFI_SUCCESS)
1023710240
continue;
10238-
/* v0.4.22 — also log PixelFormat and PixelsPerScanLine
10241+
/* v0.4.23 — also log PixelFormat and PixelsPerScanLine
1023910242
so we can see if a card (e.g. old Radeon HD 4350) only
1024010243
offers BltOnly modes (PixelFormat=3) that prevent
1024110244
direct-fb rendering. */
@@ -10250,7 +10253,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1025010253
log_line(L"[GFX] NO GOP PROTOCOL FOUND — firmware has no UEFI graphics. "
1025110254
L"Falling back to 800x600 default. UI will not render correctly.");
1025210255
}
10253-
/* v0.4.22 — MP Services Protocol diagnostic. Without this log it
10256+
/* v0.4.23 — MP Services Protocol diagnostic. Without this log it
1025410257
was impossible to tell from a field report whether multi-core
1025510258
dispatch failed (LocateProtocol error / GetNumberOfProcessors
1025610259
returned 1) or the test was simply running on a single-core
@@ -10852,7 +10855,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1085210855
g_cards[i].errors = 0;
1085310856
card_paint(i);
1085410857

10855-
/* v0.4.22 — countdown returns 0=start, 1=skip this test, 2=abort run */
10858+
/* v0.4.23 — countdown returns 0=start, 1=skip this test, 2=abort run */
1085610859
int cd_rc = countdown(2, i);
1085710860
if (cd_rc == 2) break; /* Q → abort whole run */
1085810861
if (cd_rc == 1) { /* ESC → skip this test */
@@ -10869,6 +10872,20 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1086910872
done_tests++;
1087010873
continue;
1087110874
}
10875+
/* v0.4.23 — clear the countdown footer once the test starts.
10876+
The old "[N/14] Test starts in 2 sec ..." line would linger
10877+
throughout the test run, taking up screen space without
10878+
serving any purpose during the test itself. Replace with a
10879+
short hint about how to abort. */
10880+
{
10881+
UINTN frow = g_foot_y / g_char_h;
10882+
if (frow < g_text_rows) {
10883+
clear_row(frow);
10884+
say_at_rc(0, frow,
10885+
T(L" [Q] = прервать прогон · логи пишутся в memforge2.log на USB",
10886+
L" [Q] = abort run · logs are written to memforge2.log on USB"));
10887+
}
10888+
}
1087210889

1087310890
SPrint(lb, sizeof(lb), L"[STEP 5.%d.B] Calling run_test_mc(%s)", (UINT32)i, g_tests[i].name);
1087410891
log_line(lb);
@@ -10881,8 +10898,8 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1088110898
per-test results to survive that. Cheap (1× per test, not
1088210899
1× per log line). */
1088310900
flush_log_now();
10884-
/* v0.4.22 — ACCUMULATE across marathon passes, do not OVERWRITE.
10885-
Pre-v0.4.22 the line was `g_summary[i] = r;` which kept only
10901+
/* v0.4.23 — ACCUMULATE across marathon passes, do not OVERWRITE.
10902+
Pre-v0.4.23 the line was `g_summary[i] = r;` which kept only
1088610903
the LAST pass's per-test result. On a 16-hour marathon with
1088710904
an intermittent error rate of 1 per pass, that meant the
1088810905
final summary table showed "errors: 0" because the most

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MemForge2
22

3-
**Latest release: [v0.4.22](https://github.com/Paradoxdov/memforge/releases/latest)** — download `MemForge2.efi`, copy to `EFI/BOOT/loader.efi` on a FAT32 USB.
3+
**Latest release: [v0.4.23](https://github.com/Paradoxdov/memforge/releases/latest)** — download `MemForge2.efi`, copy to `EFI/BOOT/loader.efi` on a FAT32 USB.
44

55
UEFI memory diagnostic tool for shop / repair use. Boots from USB before any
66
OS loads, runs 14 stress and pattern tests in parallel on every CPU core,
@@ -193,7 +193,7 @@ EnableAVX=1
193193
;MarathonHours=0 ; 0 = off, 1..24 = run for N hours
194194

195195
[Meta]
196-
Version=0.4.22
196+
Version=0.4.23
197197
Language=en ; "ru" or "en"
198198

199199
[Display]

quantai.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ EnableAVX=1
8282

8383
; Used by the UEFI binary for the menu language. Also used by the analyzer.
8484
[Meta]
85-
Version=0.4.22
85+
Version=0.4.23
8686
Debug=0
8787
Language=en
8888

0 commit comments

Comments
 (0)