@@ -839,7 +839,7 @@ static void init_splash(CHAR16 *stage) {
839839 cls();
840840 UINTN cy = g_h / 2;
841841 /* Title — large centered line. */
842- CHAR16 * title = L"MEMFORGE v0.4.18 " ;
842+ CHAR16 *title = L"MEMFORGE v0.4.19 ";
843843 UINTN tx = (g_w - StrLen(title) * g_char_w) / 2;
844844 gfx_draw_str_color(tx, cy - g_char_h * 2, title, COL_ACCENT_HI);
845845 /* Stage indicator — what we're doing right now. */
@@ -943,7 +943,7 @@ static UINTN g_card_cols = 1;
943943 compute_layout(). */
944944static int g_show_cards = 1;
945945
946- /* v0.4.18 — focused cards layout for small screens (g_h < 900).
946+ /* v0.4.19 — focused cards layout for small screens (g_h < 900).
947947 Instead of one full-width row per test (14 rows × ~40 px = 560 px,
948948 which on a 1024×768 screen eats 70% of vertical space and clips the
949949 core panel + footer), we draw:
@@ -1013,7 +1013,7 @@ static void compute_layout(UINTN n_tests) {
10131013 g_card_w = g_inner;
10141014 g_card_row_h = g_compact ? g_char_h : (g_char_h + 16);
10151015
1016- /* v0.4.18 — focused layout on small screens.
1016+ /* v0.4.19 — focused layout on small screens.
10171017 On g_h<900 the per-test card list eats 60-70% of vertical space
10181018 and clips the core panel / footer (YgrecK field report on 1024×768
10191019 Radeon HD 4350). Replace with: 1-row strip of all test dots +
@@ -1227,9 +1227,9 @@ static void render_header(UINT64 elapsed_ms, UINTN done, UINTN total) {
12271227 UINTN cols = g_text_cols;
12281228 if (cols >= 110) {
12291229 SPrint(buf, sizeof(buf),
1230- T (L" MEMFORGE v0.4.18 | %ld.%ld ГБ RAM | %s "
1230+ T(L" MEMFORGE v0.4.19 | %ld.%ld ГБ RAM | %s "
12311231 L"| %s | %02d:%02d | ост ~%02d:%02d | Тесты %d/%d",
1232- L" MEMFORGE v0.4.18 | %ld.%ld GB RAM | %s "
1232+ L" MEMFORGE v0.4.19 | %ld.%ld GB RAM | %s "
12331233 L"| %s | %02d:%02d | ETA ~%02d:%02d | Tests %d/%d"),
12341234 ram_gb_x10 / 10, ram_gb_x10 % 10,
12351235 pass_tag,
@@ -1239,25 +1239,25 @@ static void render_header(UINT64 elapsed_ms, UINTN done, UINTN total) {
12391239 (UINT32)done, (UINT32)total);
12401240 } else if (cols >= 90) {
12411241 SPrint(buf, sizeof(buf),
1242- T (L" MEMFORGE v0.4.18 | %ld.%ld ГБ RAM | %s | %s | %02d:%02d | ост ~%02d:%02d" ,
1243- L" MEMFORGE v0.4.18 | %ld.%ld GB RAM | %s | %s | %02d:%02d | ETA ~%02d:%02d" ),
1242+ T(L" MEMFORGE v0.4.19 | %ld.%ld ГБ RAM | %s | %s | %02d:%02d | ост ~%02d:%02d",
1243+ L" MEMFORGE v0.4.19 | %ld.%ld GB RAM | %s | %s | %02d:%02d | ETA ~%02d:%02d"),
12441244 ram_gb_x10 / 10, ram_gb_x10 % 10,
12451245 pass_tag,
12461246 err_tag,
12471247 secs / 60, secs % 60,
12481248 eta_secs / 60, eta_secs % 60);
12491249 } else if (cols >= 70) {
12501250 SPrint(buf, sizeof(buf),
1251- T (L" MEMFORGE v0.4.18 | %ld.%ld ГБ RAM | %s | %s | %02d:%02d" ,
1252- L" MEMFORGE v0.4.18 | %ld.%ld GB RAM | %s | %s | %02d:%02d" ),
1251+ T(L" MEMFORGE v0.4.19 | %ld.%ld ГБ RAM | %s | %s | %02d:%02d",
1252+ L" MEMFORGE v0.4.19 | %ld.%ld GB RAM | %s | %s | %02d:%02d"),
12531253 ram_gb_x10 / 10, ram_gb_x10 % 10,
12541254 pass_tag,
12551255 err_tag,
12561256 secs / 60, secs % 60);
12571257 } else {
12581258 SPrint(buf, sizeof(buf),
1259- T (L" MEMFORGE v0.4.18 | %s | %s | %02d:%02d" ,
1260- L" MEMFORGE v0.4.18 | %s | %s | %02d:%02d" ),
1259+ T(L" MEMFORGE v0.4.19 | %s | %s | %02d:%02d",
1260+ L" MEMFORGE v0.4.19 | %s | %s | %02d:%02d"),
12611261 pass_tag,
12621262 err_tag,
12631263 secs / 60, secs % 60);
@@ -1635,7 +1635,8 @@ static void record_error(kernel_id_t test, UINT32 core,
16351635static int dimm_label_for_addr(UINT64 addr, CHAR16 *out, UINTN out_sz_chars) {
16361636 out[0] = 0;
16371637 if (g_dimm_map_count == 0 || g_dimm_count == 0) {
1638- SPrint (out , out_sz_chars * sizeof (CHAR16 ), L"?" );
1638+ SPrint(out, out_sz_chars * sizeof(CHAR16),
1639+ T(L"вне SMBIOS-карты", L"unmapped region"));
16391640 return 0;
16401641 }
16411642 /* Collect all map entries whose address range contains this byte.
@@ -1651,7 +1652,8 @@ static int dimm_label_for_addr(UINT64 addr, CHAR16 *out, UINTN out_sz_chars) {
16511652 }
16521653 }
16531654 if (n_match == 0) {
1654- SPrint (out , out_sz_chars * sizeof (CHAR16 ), L"?" );
1655+ SPrint(out, out_sz_chars * sizeof(CHAR16),
1656+ T(L"вне SMBIOS-карты", L"unmapped region"));
16551657 return 0;
16561658 }
16571659 /* Resolve handles to locator strings. */
@@ -1664,14 +1666,14 @@ static int dimm_label_for_addr(UINT64 addr, CHAR16 *out, UINTN out_sz_chars) {
16641666 break;
16651667 }
16661668 }
1667- if (!loc || !loc [0 ]) loc = (CHAR8 * )"?" ;
1669+ if (!loc || !loc[0]) loc = (CHAR8 *)(g_lang ? "unknown DIMM" : "планка без имени") ;
16681670 pos += SPrint(out + pos, (out_sz_chars - pos) * sizeof(CHAR16),
16691671 (m == 0) ? L"%a" : L"+%a", loc);
16701672 if (pos >= out_sz_chars - 16) break;
16711673 }
16721674 if (intl_depth > 1 && n_match > 1) {
16731675 SPrint(out + pos, (out_sz_chars - pos) * sizeof(CHAR16),
1674- L" (%d -way intl)" , intl_depth );
1676+ T( L" (interleave %d×)", L" (%d -way intl)") , intl_depth);
16751677 }
16761678 return 1;
16771679}
@@ -1735,16 +1737,20 @@ static int chip_label_for_bit(UINT32 dimm_idx_0based, int bit_pos,
17351737 dimm_info_t *d = &g_dimms[dimm_idx_0based];
17361738 UINT8 dw = d->spd_device_width;
17371739 if (dw != 4 && dw != 8 && dw != 16) {
1738- /* SPD didn't tell us organization (most likely on HP Sure Start
1739- or DDR5 where we couldn't read the full block). Show bit only. */
1740- SPrint (out , out_chars * sizeof (CHAR16 ), L"bit %d (chip ?)" , bit_pos );
1740+ /* SPD didn't expose chip organization — typically on DDR4 x4 modules
1741+ where the SPD width byte wasn't readable, or on systems where
1742+ we couldn't get the full SPD block. We can't map bit→chip.
1743+ Return empty (caller should branch and use a different sentence). */
1744+ out[0] = 0;
17411745 return 0;
17421746 }
1743- /* Chip index 1-based: matches PCB silkscreen designation. */
1747+ /* Chip index 1-based: matches PCB silkscreen designation (U1..U8 / U1..U16) . */
17441748 UINT32 chip_idx = (UINT32)bit_pos / dw + 1;
17451749 UINT32 within = (UINT32)bit_pos % dw;
17461750 SPrint(out, out_chars * sizeof(CHAR16),
1747- L"chip U%d bit %d (x%d)" , chip_idx , within , dw );
1751+ T(L"чип U%d (бит %d, ширина x%d)",
1752+ L"chip U%d (bit %d, width x%d)"),
1753+ chip_idx, within, dw);
17481754 return 1;
17491755}
17501756
@@ -1777,7 +1783,7 @@ static int dominant_dimm_idx(void) {
17771783 return best;
17781784}
17791785
1780- /* v0.4.18 — detect dual-channel interleave ambiguity.
1786+ /* v0.4.19 — detect dual-channel interleave ambiguity.
17811787 On consumer desktops with dual/quad-channel memory, the iMC interleaves
17821788 addresses between channels at 64-byte (cache-line) granularity. A
17831789 SINGLE bad chip on one stick produces errors that, when mapped through
@@ -1786,7 +1792,7 @@ static int dominant_dimm_idx(void) {
17861792
17871793 Field report from a Habr user (Netac DDR4 kit): same stuck bit
17881794 D[53] was reported 24 times, distributed as A2 (8) + B2 (11) + ? (5).
1789- Pre-v0.4.18 verdict confidently said "REPLACE: DDR4-B2 (HIGH)" — but
1795+ Pre-v0.4.19 verdict confidently said "REPLACE: DDR4-B2 (HIGH)" — but
17901796 physically it's likely ONE bad chip on one of A2/B2, NOT both.
17911797
17921798 This helper returns the list of DIMM indices that each hold >=25% of
@@ -4768,15 +4774,15 @@ static void amd_thermal_probe(void) {
47684774}
47694775
47704776static UINT32 amd_thermal_sample(void) {
4771- /* v0.4.18 — correct decode per Linux k10temp / FreeBSD amdtemp.c:
4777+ /* v0.4.19 — correct decode per Linux k10temp / FreeBSD amdtemp.c:
47724778 SMN 0x59800 (SMU_THM_TCON_CUR_TMP)
47734779 bits [31:21] raw temperature value (11 bits, mask 0x7FF)
47744780 bit 19 TempRangeSel — when SET, scale is -49°C..+206°C
47754781 (subtract 49°C from the raw decode); when CLEAR
47764782 scale is 0..225°C (no offset).
47774783 temp_c = (raw * 0.125) - (range_sel ? 49 : 0)
47784784
4779- Pre-v0.4.18 code was missing both the 0x7FF mask AND the bit-19
4785+ Pre-v0.4.19 code was missing both the 0x7FF mask AND the bit-19
47804786 range adjustment, which inflated readings by ~49°C on Ryzen SKUs
47814787 that report on the -49..206 scale (most Renoir/Cezanne/Zen3+
47824788 desktop parts). Field report on Ryzen 5 4500 showed Tctl=93°C at
@@ -6528,7 +6534,7 @@ typedef struct {
65286534} card_info_t;
65296535static card_info_t g_cards[N_TESTS];
65306536
6531- /* v0.4.18 — Forward decls for focused-mode helpers (defined below
6537+ /* v0.4.19 — Forward decls for focused-mode helpers (defined below
65326538 card_paint so they can share the same color-lookup logic). */
65336539static void card_paint_full(UINTN i);
65346540static void card_strip_paint(UINTN i);
@@ -6642,7 +6648,7 @@ static void card_paint_full(UINTN i) {
66426648 }
66436649}
66446650
6645- /* ---------- Focused-mode card painters (v0.4.18 ) ---------- */
6651+ /* ---------- Focused-mode card painters (v0.4.19 ) ---------- */
66466652
66476653/* Paint the small status dot for test i in the top strip. The strip is
66486654 one row tall and shows N evenly-spaced dots, one per test. The dot
@@ -7766,11 +7772,23 @@ static int verdict_describe_what_broke(CHAR16 *line1, UINTN cap1,
77667772 int bp = single_bit_pos(stuck_x);
77677773 if (stuck_n >= 5 && bp >= 0 && dominant_dimm_0based >= 0) {
77687774 CHAR16 chip[64];
7769- chip_label_for_bit ((UINT32 )dominant_dimm_0based , bp , chip , 64 );
7770- SPrint (line1 , cap1 ,
7771- T (L"• %s — бит %d застрял (%d ошибок этого типа)" ,
7772- L"• %s — bit %d stuck (%d errors of this type)" ),
7773- chip , bp , stuck_n );
7775+ int have_chip = chip_label_for_bit((UINT32)dominant_dimm_0based, bp, chip, 64);
7776+ if (have_chip) {
7777+ /* SPD told us chip width → can name the exact chip on PCB */
7778+ SPrint(line1, cap1,
7779+ T(L"● Дохлая ячейка: %s — биту %d стуковое значение (%d ошибок этого типа)",
7780+ L"● Dead cell: %s — bit %d stuck (%d errors of this type)"),
7781+ chip, bp, stuck_n);
7782+ } else {
7783+ /* SPD didn't expose chip width (typical for DDR4 x4 / некоторые DDR5)
7784+ — we know which bit, just not which physical chip on the PCB */
7785+ SPrint(line1, cap1,
7786+ T(L"● Стуковый бит D[%d] на планке (%d ошибок). "
7787+ L"SPD не сообщил ширину чипа — точный U-номер чипа не определить.",
7788+ L"● Stuck bit D[%d] on DIMM (%d errors). "
7789+ L"SPD did not expose chip width — exact chip U-number unknown."),
7790+ bp, stuck_n);
7791+ }
77747792 n++;
77757793 }
77767794
@@ -7780,8 +7798,8 @@ static int verdict_describe_what_broke(CHAR16 *line1, UINTN cap1,
77807798 CHAR16 *target = (n == 0) ? line1 : line2;
77817799 UINTN cap = (n == 0) ? cap1 : cap2;
77827800 SPrint(target, cap,
7783- T (L"• Повреждён ряд ячеек ~0x%lx (%d ошибок в одном ряду)" ,
7784- L"• Damaged cell row ~0x%lx (%d errors in same row)" ),
7801+ T(L"● Повреждён ряд ячеек ~0x%lx (%d ошибок в одном ряду)",
7802+ L"● Damaged cell row ~0x%lx (%d errors in same row)"),
77857803 srow, srow_n);
77867804 n++;
77877805 if (n >= 2) return n;
@@ -7793,8 +7811,8 @@ static int verdict_describe_what_broke(CHAR16 *line1, UINTN cap1,
77937811 CHAR16 *target = (n == 0) ? line1 : line2;
77947812 UINTN cap = (n == 0) ? cap1 : cap2;
77957813 SPrint(target, cap,
7796- T (L"• Повреждена секция памяти (bank-group %d / bank %d, %d ошибок)" ,
7797- L"• Damaged memory bank (bank-group %d / bank %d, %d errors)" ),
7814+ T(L"● Повреждена секция памяти (банк-группа %d / банк %d, %d ошибок)",
7815+ L"● Damaged memory bank (bank-group %d / bank %d, %d errors)"),
77987816 (sbank >> 4) & 0xF, sbank & 0xF, sbank_n);
77997817 n++;
78007818 if (n >= 2) return n;
@@ -7803,8 +7821,8 @@ static int verdict_describe_what_broke(CHAR16 *line1, UINTN cap1,
78037821 /* Fallback when no clear pattern — say total error count + dominant DIMM */
78047822 if (n == 0) {
78057823 SPrint(line1, cap1,
7806- T (L"• %ld ошибок памяти без чёткой локализации" ,
7807- L"• %ld memory errors without a clear pattern" ),
7824+ T(L"● %ld ошибок памяти без чёткой локализации",
7825+ L"● %ld memory errors without a clear pattern"),
78087826 (UINT64)g_err_count);
78097827 }
78107828 return n;
@@ -7964,7 +7982,7 @@ static void render_simple_verdict(UINT64 total_ms) {
79647982 }
79657983 } else { /* VERDICT_FAIL */
79667984 int didx = dominant_dimm_idx();
7967- /* v0.4.18 — interleave detection.
7985+ /* v0.4.19 — interleave detection.
79687986 If errors are distributed across 2+ DIMMs (typical dual-channel
79697987 interleave hiding a single bad chip behind two DIMM labels),
79707988 we MUST NOT confidently name one DIMM. Verdict instead tells
@@ -8187,8 +8205,8 @@ static void render_summary(UINT64 total_ms) {
81878205 UINTN hrow = (g_hdr_h / 2 - g_char_h / 2) / g_char_h;
81888206 CHAR16 buf[200];
81898207 SPrint(buf, sizeof(buf),
8190- T (L" MEMFORGE v0.4.18 ИТОГИ | %d сек | Ядра %d/%d" ,
8191- L" MEMFORGE v0.4.18 SUMMARY | %d sec | Cores %d/%d" ),
8208+ T(L" MEMFORGE v0.4.19 ИТОГИ | %d сек | Ядра %d/%d",
8209+ L" MEMFORGE v0.4.19 SUMMARY | %d sec | Cores %d/%d"),
81928210 (UINT32)(total_ms / 1000),
81938211 (UINT32)g_n_enabled, (UINT32)g_n_cores);
81948212 say_at_rc(0, hrow, buf);
@@ -8271,15 +8289,22 @@ static void render_summary(UINT64 total_ms) {
82718289 if (didx >= 0)
82728290 chip_label_for_bit((UINT32)didx, bp, chip, 64);
82738291 if (didx >= 0 && chip[0]) {
8292+ /* Full info: DIMM + exact chip designator */
82748293 SPrint(sb, sizeof(sb),
8275- T (L"⚠ Застрял бит D[%d] → DIMM%d %s: %d ошибок" ,
8276- L"⚠ Stuck bit D[%d] → DIMM%d %s: %d errors" ),
8294+ T(L"⚠ Застрял бит D[%d] → DIMM%d, %s: %d ошибок",
8295+ L"⚠ Stuck bit D[%d] → DIMM%d, %s: %d errors"),
82778296 bp, didx + 1, chip, stuck_n);
8297+ } else if (didx >= 0) {
8298+ /* DIMM known, exact chip not — say so plainly */
8299+ SPrint(sb, sizeof(sb),
8300+ T(L"⚠ Застрял бит D[%d] → DIMM%d (точный чип не определён по SPD): %d ошибок",
8301+ L"⚠ Stuck bit D[%d] → DIMM%d (exact chip unknown per SPD): %d errors"),
8302+ bp, didx + 1, stuck_n);
82788303 } else {
82798304 SPrint(sb, sizeof(sb),
8280- T (L"⚠ Застрял бит D[%d]: %d ошибок с XOR=0x%016lx " ,
8281- L"⚠ Stuck bit D[%d]: %d errors with XOR=0x%016lx " ),
8282- bp , stuck_n , stuck_x );
8305+ T(L"⚠ Застрял бит D[%d] (планку определить не удалось) : %d ошибок",
8306+ L"⚠ Stuck bit D[%d] (DIMM could not be identified) : %d errors"),
8307+ bp, stuck_n);
82838308 }
82848309 } else {
82858310 SPrint(sb, sizeof(sb),
@@ -8321,9 +8346,9 @@ static void render_summary(UINT64 total_ms) {
83218346 }
83228347
83238348 /* (2) Affected-DIMM list (from SMBIOS Type 20 mapping). */
8324- CHAR16 dimm_line [220 ]; UINTN pos = 0 ;
8349+ CHAR16 dimm_line[260 ]; UINTN pos = 0;
83258350 pos += SPrint(dimm_line + pos, sizeof(dimm_line) - pos * sizeof(CHAR16),
8326- T (L"Затронуто : " , L"Affected DIMMs : " ));
8351+ T(L"Ошибки по планкам : ", L"Errors by DIMM : "));
83278352 /* Aggregate: tally errors per distinct DIMM-label string. */
83288353 CHAR16 labels[8][64]; UINT32 lcount[8] = {0}; UINT32 nl = 0;
83298354 UINT32 shown = g_err_count > MAX_ERR_RECORDS ? MAX_ERR_RECORDS : g_err_count;
@@ -9968,7 +9993,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
99689993 }
99699994 }
99709995
9971- log_line (L"=== MemForge2 v0.4.18 init ===" );
9996+ log_line(L"=== MemForge2 v0.4.19 init ===");
99729997 log_line(L"[WATCHDOG] UEFI 5-min watchdog disabled at app entry");
99739998 /* Show splash IMMEDIATELY so the user sees the program is alive while
99749999 INI parsing, SMBus probes and SMBIOS walk happen. Without this, the
@@ -10013,7 +10038,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1001310038 if (uefi_call_wrapper(g_gop->QueryMode, 4,
1001410039 g_gop, m, &info_sz, &info) != EFI_SUCCESS)
1001510040 continue;
10016- /* v0.4.18 — also log PixelFormat and PixelsPerScanLine
10041+ /* v0.4.19 — also log PixelFormat and PixelsPerScanLine
1001710042 so we can see if a card (e.g. old Radeon HD 4350) only
1001810043 offers BltOnly modes (PixelFormat=3) that prevent
1001910044 direct-fb rendering. */
@@ -10028,7 +10053,7 @@ EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
1002810053 log_line(L"[GFX] NO GOP PROTOCOL FOUND — firmware has no UEFI graphics. "
1002910054 L"Falling back to 800x600 default. UI will not render correctly.");
1003010055 }
10031- /* v0.4.18 — MP Services Protocol diagnostic. Without this log it
10056+ /* v0.4.19 — MP Services Protocol diagnostic. Without this log it
1003210057 was impossible to tell from a field report whether multi-core
1003310058 dispatch failed (LocateProtocol error / GetNumberOfProcessors
1003410059 returned 1) or the test was simply running on a single-core
0 commit comments