The Satellite Corporation BUILT FOR FULLSCREEN

0

SATELLITE UPDATE

I HAVE JUST FINISHED DESIGNING “SATELLITE 004 REVISION 01”; ALTHOUGH SATELLITE IS NOT GOING TO BE RELEASED FOR ANOTHER 2 MONTHS, THE CURRENT DESIGN, AND IT WON’T GET ANY SLOWER THAN IT IS NOW… IS x119 FASTER THAN SATELLITE 003 REVISION 06.

THE REASON FOR IT IS THAT WE ARE COMPILING ALL OF THESE TINY C++ LIBRARIES, FIRST WE BUILD THE FUNCTION THAT RUNS FOR THAT satellite.something.something(), THEN WE BUILD THE MOST LIKELY ARGUMENTS TO BE INCLUDED… SO WHEN YOU CALL satellite.console.display(“some string”) IT RUNS LITERALLY AS FAST AS C++, AND SOME TIMINGS FOR IT RACING std::cout CAN BE –FASTER– THAN COMPILED C++ CODE, IT JUST KINDA GETS LUCKY IN THE TIMING AND IT COMES OUT FASTER;

I WAS KINDA STARTING TO NOT LIKE THE 003 06 INTERPRETER, AND AS I BEGAN TO ADD POLYMORPHIC CODE TO IT, I REALIZED I CAN JUST BUILD ALL OF THESE TINY C++ LIBRARIES THAT IT LOADS AHEAD OF TIME.. SO SATELLITE IS GOING TO BE SLIGHTLY SLOW FOR SOMETHING THAT’S LIKE THIS:

satellite.include(satellite)

satellite.capsule satellite.main()
{
  satellite.console.display("welcome to my program")
  
  satellite.variable.number some_value = 999999
  
  satellite.variable.number another_value = 2344763
  
  satellite.console.display(some_value + another_value)
  
  satellite.return(satellite)
}

I’M GOING TO BE COMPLETELY HONEST WITH YOU — IF YOU ARE THINKING ABOUT RUNNING SOME CODE THAT LOOKS LIKE THAT, YOU MIGHT AS WELL RUN IT IN PYTHON OR C++ — CODE LIKE THAT DOESN’T NEED TO LOAD 1,000 TINY C++ PROGRAMS, SATELLITE IS BEING BUILT FOR PROGRAMS THAT ARE BUILT OUT OF; 100,000 LINES, 20,000 LINE PROGRAMS, THINGS LIKE THAT.

YOU CAN TECHNICALLY USE IT FOR SMALL PROGRAMS, BUT WHY LOAD 1,000 TINY LIBRARIES TO ADD TWO NUMBERS TOGETHER?

ANYWAYS, FOR LIKE REALLY LARGE PROGRAMS, SATELLITE WILL RUN INCREDIBLY FAST.. THE EXACT SPEED IS GOING TO BE LIKE LITERALLY AS FAST, BUT FASTER, HERE IS WHY IT’S FASTER THAN COMPILED C++:

I'VE GOT AHOLD OF A GENERAL ARTIFICIAL INTELLIGENCE (LIKE A SUPER ONE) AND IT IS PROGRAMMING ALL OF THE TINY C++ LIBRARIES TO BE AS FAST AS THEY CAN POSSIBLY GO!!

SO IF YOU ARE GOING TO BUILD A PROGRAM THAT SAY, DOES JUST A _TON_ OF NORMAL OPERATIONS, YOUR CODE IS GOING TO RUN FASTER IF YOU USE SATELLITE THAN IF YOU WROTE IT YOURSELF, BECAUSE THE GENERAL ARTIFICIAL INTELLIGENCE KNOWS HOW TO WRITE BETTER C++ THAN YOU DO. WE ARE TAKING OUR TIME AND WRITING EACH TINY C++ LIBRARY TO BE AS FAST AS WE CAN MAKE IT, WE MIGHT STRIP “int main()” OUT OF EACH LIBRARY, I DON’T KNOW YET, I GUESS WE DON’T NEED TO TAKE IT THAT FAR, BUT THAT JUST GIVES YOU AN EXAMPLE OF THE LENGTHS THAT WE ARE GOING TO HERE…

SO FOR A LOT OF NORMAL COMPUTER CODE, SATELLITE WILL BE THE BEST SOLUTION FOR LOTS OF NORMAL COMPUTER CODE… NOW IF YOUR PROGRAMMING SOMETHING EXTREMELY COMPLICATED, LIKE SOMETHING WITH POINTERS, NAMESPACES, TEMPLATES, AND YOU UNDERSTAND ALL OF THAT STUFF, THEN C++ IS GOING TO BE THE BETTER OPTION.

THE ORIGINAL GOAL OF SATELLITE HAD JUST A SINGLE THING IN MIND: TO MAKE A PROGRAMMING LANGUAGE TO OVERCOME C++, SO THAT OUR SOCIETY DIDN’T HAVE TO USE C++; ALTHOUGH SATELLITE IS BUILT IN C++, WHICH MEANS IT WILL NEVER BECOME IRRELEVANT, IT IS BUILT TO CALL C++; SO IN OTHER WORDS, IT’S SUPPOSED TO BE USED INSTEAD OF C++, THAT IS LIKE, THE POINT OF THE ENTIRE PROJECT, BUT WHATEVER.

IT’S SO WE DON’T HAVE TO USE C++ ANYMORE, C++ STILL HAS LIKE EVERYTHING FROM 1994, AND YES IT STILL ALL WORKS, AND IT’S PROBABLY ONLY GOTTEN FASTER IF YOU STILL HAVE PROGRAMS FROM 1994, BUT IF YOUR ON A MACHINE THAT HAS A GUI AND ALMALINUX 10, WHY WOULD YOU CHOOSE C++ AS THE LANGUAGE TO USE? YOU HAVE A GUI, YOU HAVE THE MEMORY AVAILABLE, YOU HAVE THE PROCESSING POWER AVAILABLE, USE SOMETHING DESIGNED TO USE THESE THINGS, SOMETHING DESIGNED WITH ALL OF THESE THINGS IN MIND?

C++ CAN DO ANYTHING, IT CAN DO IT INCREDIBLY FAST, IT HAS ALL OF THE CONTROL, BUT IF YOU DON’T REALLY NEED THAT STUFF, AND YOUR JUST DOING SOME NORMAL STUFF, YOU DON’T NEED TO LOOK UP HOW TO WRITE A STD::CHRONO DURATION_CAST 6 TIMES TO WRITE A TIMER, YOU CAN JUST WRITE:

satellite.include(satellite)

satellite.capsule satellite.main(satellite.container.list<satellite.variable.string> arguments)
{
  satelllite.variable.time my_timer = satellite.time.now()
  
  // wait, that isn't a timer, but I know what i'll make the syntax for a timer,
  
  satellite.variable.time my_timer = satellite.time.timer()
  
  my_timer.start()
  
  my_timer.end()
  
  satellite.return(satellite)
}

AND THAT WILL BE THE ENTIRE TIMER, WITH ALL OF THE HIGH RESOLUTION OF NANOSECONDS, IF YOU WANT THEM, YOU’LL BE ABLE TO DECLARE WHAT TYPE OF TIME YOU WANT, INPUTTING IT RIGHT INTO THE satellite.time.timer(“ms”) or “s” or “ns” or whatever, you can just do that. So here is an entire nanosecond timer in C++, with the time displayed:

satelllite.include(satellite)

satellite.capsule satellite.main(satellite.container.list<satellite.variable.string> arguments)
{
  satellite.variable.time my_timer = satellite.time.timer("ns")
  
  my_timer.start()
  
  satellite.console.display("put the code you will run in between .start() and .end()")
  
  my_timer.end()
  
  satellite.console.display(my_timer)
  
  satellite.return(satellite)
}

that’s all it’s going to take dude, no lies, and no bullshit it’s tiny compiled C++ libraries, so satellite can’t replace C++, it’s built using C++, it’s not going to run on someones watch, or inside of an MP3 Player, it’s made for large machines, but if you do have a large machine, perhaps with infinite amounts of memory and processing power, use all of that processing power, and use all of those threads, and use your graphics card (someday)!

We stupid people will be able to program graphical applications in satellite, things that are like little dots running around the screen, and someday I am using it for my artificial intelligence, QUAD AI, that is the literal reason why I have created it, but I built it to be able to take advantage of: large machines, that have like infinite resources, those resources can be maximized by satellite, it cannot program “large CERN particle colliders” but the tradeoff of not being able to do that stuff is in what it can actually do — do you have 32 – 64 gigabytes of memory? More? Then you can use satellite, but the tradeoff is that if you are trying to balance the tires on your car on a budget inside of a small chip that exists inside of a car, then no, your not going to be able to run satellite in that location, so I guess the moral of the story is that it doesn’t replace C++ when it’s intended goal is to replace C++, kind of a weird way to think about it?

Oh well.

1

SATELLITE TUTORIAL FOR BEGINNERS

I WROTE THIS TUTORIAL FOR MY MOM TO FOLLOW, SHE IS STILL LEARNING TO PROGRAM THE COMPUTER, SO I MADE THIS TUTORIAL EASY. SO THAT SHE COULD STILL FOLLOW IT BECAUSE SHE IS STILL LEARNING ABOUT PROGRAMMING, BUT THIS IS ALSO A PERFECT PLACE TO START FOR SOMEONE WHO IS A BEGINNER.

FIRST, TO RUN SATELLITE PROGRAMS, YOU NEED TO INSTALL SATELLITE. GO TO THE DOWNLOAD BUTTON AT THE TOP OF THIS WEBSITE, AND THEN CLICK DOWNLOAD. AFTER YOU DOWNLOAD THE APPLICATION, YOU NEED TO LOOK WHERE YOU SAVED IT AND EXTRACT THE FILES.

THEN YOU NEED TO OPEN A CONSOLE WINDOW IN THE SAME FOLDER AS THE EXTRACTED FILES. THIS IS BECAUSE WE WILL NEXT RUN THE INSTALL SCRIPT CALLED “install-satellite.sh” BY TYPING THIS IN TO THE CONSOLE:

./install-satellite.sh

TYPE THAT IN TO THE CONSOLE AFTER YOU HAVE CD’d TO THE SAME DIRECTORY THAT THE EXTRACTED FILES ARE IN. THESE FILES ARE IN A FOLDER THAT IS NEXT TO THE FILE THAT YOU DOWNLOADED. ALL THAT YOU HAVE TO DO IS OPEN A CONSOLE WINDOW INSIDE OF THAT FOLDER, AND TYPE IN “./install-satellite.sh” AND IT WILL INSTALL THE PROGRAM FOR YOU, THEN YOU NEED TO SET YOUR $PATH VARIABLE INSIDE OF YOUR “.bashrc” FILE.

THIS WILL ALLOW YOU TO ACCESS SATELLITE FROM ANY CONSOLE WINDOW, AND IT ALSO COMES WITH A PROGRAM CALLED “satl-term”, WHICH IS A TERMINAL DESIGNED ONLY FOR RUNNING SATELLITE PROGRAMS. YOU CAN ACCESS THE “satl-term” PROGRAM BY USING THE SAME $PATH VARIABLE THAT WE SET EARLIER, JUST ADD THIS COMMAND TO YOUR .bashrc FILE:

export PATH="/~/.satl/bin:$PATH"

AFTER YOU HAVE TYPED THAT INTO YOUR .bashrc FILE, CLOSE ANY CONSOLE WINDOWS THAT YOU HAVE OPEN TO REFRESH YOUR VARIABLES AND OPEN THEM AGAIN, YOU WILL NOW HAVE ACCESS TO TWO SEPARATE PROGRAMS:

"satl" -- THE NAME OF THE INTERPRETER
"satl-term" -- THE NAME OF THE TERMINAL FOR RUNNING SATELLITE PROGRAMS

OPTIONALLY YOU CAN SET THE NAME “satellite” TO ALSO RUN SATELLITE PROGRAMS, THAT IS UP TO YOU, THE WORD “SATELLITE” MAY BE RESERVED BY REDHAT OR DEBIAN, BUT ESPECIALLY ON ENTERPRISE LINUX THE WORD “SATELLITE” IS RESERVED FOR THEIR PROGRAMS, BUT IF YOU DON’T USE REDHAT SATELLITE, THEN IT MIGHT NOT HURT ANYTHING.

NOW THAT WE HAVE INSTALLED THE INTERPRETER FOR SATELLITE PROGRAMS, WE CAN WRITE ONE! TYPE THESE LINES INTO A TEXT FILE AND CLICK SAVE AS, THEN TYPE IN A NAME FOR IT LIKE “hello_world.satl” OR SOMETHING SIMILAR, BUT ALSO REMEMBER WHERE YOU SAVED THE FILE, WE WILL NEED TO GO THERE TO RUN THE PROGRAM, WE WILL HAVE TO TYPE IN THE FULL ADDRESS OF THE PROGRAM TO RUN IT. SO FIRST THE SYNTAX THAT YOU NEED TO TYPE IN: (AND DON’T WORRY ABOUT THE COMMENTS FOR RIGHT NOW, THEY ARE JUST THERE TO TELL YOU ABOUT THE DIFFERENT LINES, AND YOU DON’T REALLY HAVE TO UNDERSTAND EVERY SINGLE ONE OF THEM.)

// this is a comment, you dont need to type in the comments.

// all satellite programs include satellite
satellite.include(satellite)

// a function in satellite is called a "capsule" and your program starts from the "main" capsule. We declare our "main" capsule with the words "satellite.main"
satellite.capsule satellite.main()
{
  // the program begins on the first line of the "satellite.main" capsule
  
  // this line is how you display text on to your screen!
  satellite.console.display("hello, world!")
  
  // after you run this program, you should see only the words "hello, world!"
  
  // you can type in anything in between the parentheses, and it will display the text.
  
  // this line ends the program, and it will close the interpreter for you
  satellite.return(satellite)
}

THIS CONCLUDES THE BEGINNER SATELLITE TUTORIAL!

THE SCRIPT “install-satellite.sh” INCLUDES EVERYTHING THAT THE REAL INSTALLER INCLUDES, AND IT CHANGES YOUR FILES TO LOOK LIKE PIECES OF PAPER WITH SATELLITES ON THEM.

I AM STILL WORKING ON PROGRAMS THAT OFFER SYNTAX HIGHLIGHTING, I JUST DON’T HAVE THE TIME TO GET EVERYTHING WORKING FOR IT, GTK4 PROGRAMS ARE VERY COMPLICATED.

THIS PAGE DOESN’T OFFER SYNTAX HIGHLIGHTING FOR THE SATELLITE LANGUAGE BECAUSE I DON’T HAVE THE TIME TO PROGRAM SYNTAX HIGHLIGHTING FOR IT MYSELF.

0

Universal Linux Kernel Build Script

This script works on enterprise linux, and it’s supposed to work on debian/ubuntu too. It grabs the latest kernel available, you just have to run it twice to install the kernel; First here’s the instructions:

cd to the directory containing the script (after you extract it)

run the script:
./universal_linux_kernel_build.sh

after the script is done running and it builds the kernel, type in this command:
export INSTALL=1

Then we have to run the script with the variable INSTALL set to 1:
./universal_linux_kernel_build.sh

If you happen to have an AMD Graphics Card, it will build AMDGPU for you as well! Here’s the download button:

Here is the complete build script for you to look at but I wouldn’t copy and paste it, just use the “Download” button and extract it. If you copy and paste it, some of the characters will not be copied and it might not work. You can compare the number of lines and file size of both though, if your worried about viruses!

#!/usr/bin/env bash
#
# universal_linux_kernel_build.sh
#
# Download, configure, build and install a mainline Linux kernel that works on
# whatever graphics you actually have -- AMD, NVIDIA, Intel, a virtual machine,
# or a headless server -- with the GPU compute and NPU ("AI") paths turned on.
#
# It starts from your running distro's config so your existing hardware keeps
# working, then enforces the options GPU compute needs and that hand-rolled
# configs most often get wrong.
#
#   ./universal_linux_kernel_build.sh                 # detect, build only
#   INSTALL=1 ./universal_linux_kernel_build.sh       # build, then install
#
#   GPU=auto        detect from lspci (default)
#   GPU=all         every vendor -- a kernel that boots on anything
#   GPU=amd,intel   a comma-separated list: amd nvidia intel vm
#
#   KVER=6.12.9 ./universal_linux_kernel_build.sh     # a specific release
#   SRC=~/src/linux ./universal_linux_kernel_build.sh # an existing tree
#   TOOLCHAIN=llvm LLVM_PREFIX=$HOME/opt/clang-current/bin/ ./universal_...sh
#
#   CLEAN=auto      mrproper the tree only if the last attempt did not finish
#   CLEAN=always    mrproper first, every time
#   CLEAN=never     never clean; always build incrementally
#
#   CONFIG_BASE=auto      keep the tree's .config if it has one, else the distro's
#   CONFIG_BASE=distro    always reseed from /boot/config-$(uname -r)
#   CONFIG_BASE=defconfig start from the arch defconfig
#   CONFIG_BASE=<path>    start from a config file you name
#   FRAME_WARN=8192 -Wframe-larger-than=; 0 disables the check entirely
#
# Two files are written beside this script (override with STATE_FILE= / BUILD_LOG=):
#   kernel_build.ini   what was done to each source tree, one [section] each
#   kernel_build.log   full output of the last build (previous kept as .log.prev)
#
# THE ONE THING THIS SCRIPT EXISTS TO GET RIGHT: GPU drivers are MODULES.
# Built-in (=y) is the most common mistake in hand-rolled GPU kernel configs.
# A built-in driver probes before the root filesystem is mounted, so all of its
# firmware must already be in the initramfs -- but dracut and initramfs-tools
# decide which firmware to include by looking at which MODULES are installed,
# and a built-in driver is not a module. The result is a driver that cannot load
# its firmware: no display, no /dev/kfd. As modules they also stay unloadable
# and blacklistable, which the NVIDIA proprietary stack requires.
#
# Licence: public domain / CC0. No warranty. It installs a kernel; read it first.

set -euo pipefail

# --- integrity self-check -------------------------------------------------
# This file must be pure ASCII.  Copying a script out of a web page or a chat
# window silently replaces ordinary spaces with U+00A0 (non-breaking space),
# which bash treats as part of a command name -- you get "command not found"
# on a line that looks perfectly correct.
#
# NOTE: the body below is deliberately NOT indented.  Leading whitespace is
# exactly what gets converted, so an indented error handler is unable to report
# the corruption it just detected -- it fails as "printf: command not found".
if LC_ALL=C tr -d '\11\12\40-\176' < "$0" | head -c 1 | LC_ALL=C grep -q .; then
printf 'ERROR: %s contains non-ASCII bytes.\n' "$0" >&2
printf 'It was almost certainly corrupted by copy-paste from a web page.\n' >&2
printf 'Repair it with:\n' >&2
printf "    sed -i 's/%s/ /g' %s\n" '\xc2\xa0' "$0" >&2
printf 'or, better, download the file directly instead of pasting it.\n' >&2
exit 1
fi
# --------------------------------------------------------------------------

GPU="${GPU:-auto}"
KVER="${KVER:-}"                                  # empty = latest stable
SRC="${SRC:-}"                                    # empty = download a tarball
WORK="${WORK:-$HOME/src}"
LOCALVERSION="${LOCALVERSION:--gpu}"
FRAME_WARN="${FRAME_WARN:-8192}"     # -Wframe-larger-than=; 0 disables, 8192 is the Kconfig max
# De-export it.  This is OUR knob, but kbuild has a variable of the SAME NAME
# and means something else by it: scripts/setlocalversion ends with
#     echo "${KERNELVERSION}${file_localversion}${config_localversion}${LOCALVERSION}${scm_version}"
# so a LOCALVERSION that is in make's ENVIRONMENT is appended on top of the
# CONFIG_LOCALVERSION this script sets below -- and the release comes out as
# 7.2.0-gpu-gpu.  It only bites when the value arrived from the environment
# (an orchestrator, or `LOCALVERSION=-x ./this.sh`), because a plain assignment
# does not export, but a value inherited from the environment stays exported
# through re-assignment.  The config setting below is the only path we want.
export -n LOCALVERSION 2>/dev/null || true
# ------------------------------------------------------------- build bookkeeping
# Two files live next to this script.  kernel_build.ini records what was done to
# each source tree so a later run can tell an interrupted or failed tree from a
# finished one; kernel_build.log is the full output of the last build.
#
# The .ini is keyed by SOURCE TREE, not by version -- one [section] per tree --
# because a single machine easily ends up with several (a KVER= build in ~/src
# beside an SRC=/KERNEL_SRC= tree elsewhere), and a state file that assumed one
# tree would confidently report the wrong one.  Delete it any time: a missing
# entry simply reads as "previous attempt unknown".
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" || SELF_DIR="$PWD"
STATE_FILE="${STATE_FILE:-$SELF_DIR/kernel_build.ini}"
BUILD_LOG="${BUILD_LOG:-$SELF_DIR/kernel_build.log}"
CLEAN="${CLEAN:-auto}"    # auto: clean only an unfinished tree | always | never
# Where the configuration comes from.  "auto" keeps a .config the tree already
# has and only seeds from the running distro when there is none, so re-running
# no longer throws away work between runs.  The enforcement below re-applies to
# whatever base is chosen, and the verify block still refuses to install a
# config that did not come out right, so keeping an existing one is safe.
CONFIG_BASE="${CONFIG_BASE:-auto}"   # auto | distro | defconfig | /path/to/config
# Both are touched only AFTER this script has chdir'd into $WORK and then $SRC,
# so a relative override would land somewhere the user did not mean.  Resolve
# them against the directory we were invoked from, once, while that is still cwd.
case $STATE_FILE in /*) ;; *) STATE_FILE="$PWD/$STATE_FILE" ;; esac
case $BUILD_LOG  in /*) ;; *) BUILD_LOG="$PWD/$BUILD_LOG"   ;; esac
JOBS="${JOBS:-$(nproc)}"
INSTALL="${INSTALL:-0}"
INSTALL_DEPS="${INSTALL_DEPS:-1}"
TOOLCHAIN="${TOOLCHAIN:-gcc}"                     # gcc | llvm
LLVM_PREFIX="${LLVM_PREFIX:-}"                    # e.g. $HOME/opt/clang-current/bin/  (TRAILING SLASH)
KEEP_DEBUG_INFO="${KEEP_DEBUG_INFO:-0}"           # 1 keeps DWARF+BTF (far bigger, far slower)

say()  { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[33mwarning:\033[0m %s\n' "$*" >&2; }
die()  { printf '\n\033[31mERROR:\033[0m %s\n' "$*" >&2; exit 1; }

# ---------------------------------------------------------------- state helpers
# Loaded into an associative array and flushed whole, rather than edited in
# place.  Sections for OTHER trees are copied through verbatim, so two trees
# never clobber each other, and a flush after every set means the record
# survives a kill -9 mid-build -- which is exactly the case it exists to detect.
declare -A STATE=()
STATE_OK=0
STATE_STAGE=startup

state_init() {
    local d; d="$(dirname "$STATE_FILE")"
    if [ -d "$d" ] && [ -w "$d" ] && { [ ! -e "$STATE_FILE" ] || [ -w "$STATE_FILE" ]; }; then
        STATE_OK=1
    else
        warn "cannot write $STATE_FILE -- continuing without build-state tracking.
       Set STATE_FILE=<path> to somewhere writable, or pass CLEAN=always/never
       to decide about cleaning explicitly."
    fi
}

state_load() {
    [ "$STATE_OK" = 1 ] && [ -f "$STATE_FILE" ] || return 0
    local line k v inm=0
    while IFS= read -r line; do
        case "$line" in
            "[$SRC]") inm=1; continue ;;
            \[*)      inm=0; continue ;;
            ''|'#'*)  continue ;;
        esac
        [ "$inm" = 1 ] || continue
        case "$line" in *=*) ;; *) continue ;; esac
        k=${line%%=*}; v=${line#*=}
        k=${k// /}; k=${k//$'\t'/}; v=${v# }
        # An if, not `[ -n "$k" ] && ...`: a while-read returns the status of the
        # last command in its body, so a trailing blank key would make state_load
        # return 1 and `set -e` would kill the run silently at the call site.
        if [ -n "$k" ]; then STATE["$k"]="$v"; fi
    done < "$STATE_FILE"
    return 0
}

state_flush() {
    [ "$STATE_OK" = 1 ] || return 0
    local tmp k
    tmp="$(mktemp "${STATE_FILE}.XXXXXX" 2>/dev/null)" || return 0
    {
        printf '# universal_linux_kernel_build.sh -- build state, one [section] per source tree.\n'
        printf '# Written automatically; safe to delete.\n'
        # cat -s squeezes the blank line that the awk pass preserves and the
        # printf below re-emits; without it the file gains one blank per write.
        [ -f "$STATE_FILE" ] && awk -v sec="[$SRC]" '/^\[/ { keep = ($0 != sec) } keep' "$STATE_FILE" | cat -s
        printf '\n[%s]\n' "$SRC"
        for k in "${!STATE[@]}"; do printf '%s = %s\n' "$k" "${STATE[$k]}"; done | sort
    } > "$tmp" 2>/dev/null && mv -f "$tmp" "$STATE_FILE" || rm -f "$tmp"
}

state_set() { [ "$STATE_OK" = 1 ] || return 0; STATE["$1"]="$2"; state_flush; }

# Records a failure without changing the exit status.  `local rc=$?` must stay
# the first statement or it captures the wrong command.
_state_on_exit() {
    local rc=$?
    if [ "$STATE_OK" = 1 ] && [ "$rc" -ne 0 ]; then
        STATE[status]=failed
        STATE[stage]="$STATE_STAGE"
        STATE[exit_code]="$rc"
        STATE[finished]="$(date -Is 2>/dev/null || date 2>/dev/null || true)"
        state_flush
    fi
    return 0
}

DNF_PKGS="gcc make flex bison bc openssl-devel elfutils-libelf-devel dwarves
          ncurses-devel rsync cpio perl python3 xz zstd git tar pciutils"
APT_PKGS="build-essential flex bison bc libssl-dev libelf-dev dwarves
          libncurses-dev rsync cpio perl python3 xz-utils zstd git tar pciutils"

# Best-effort, deliberately.  A package manager exits non-zero for reasons that
# have nothing to do with the packages asked for -- one third-party repo with an
# unverifiable GPG key is enough, and dnf then refuses to load metadata and
# exits 1 in the first ten seconds.  Under `set -e` a bare install call would
# take a multi-hour build down with it for a reason the user cannot see from the
# error.  So warn and carry on: the Preflight loop immediately below is the real
# gate, and it names the tool that is actually missing.
if [ "$INSTALL_DEPS" = 1 ]; then
    say "Installing build dependencies (sudo)"
    if command -v dnf >/dev/null; then
        sudo dnf install -y $DNF_PKGS \
            || warn "dnf could not install every dependency; continuing.
       If the build fails on a missing header, that is where to look.  A single
       broken repo is the usual cause -- retry with
           sudo dnf install -y --disablerepo=<broken> $DNF_PKGS
       or set INSTALL_DEPS=0 if you know the packages are already present."
    elif command -v apt-get >/dev/null; then
        # Separate statements, not `update && install`: chained, a failed update
        # makes the whole list non-zero and set -e kills the script with nothing
        # printed.  A stale index is not a reason to refuse to build.
        sudo apt-get update || warn "apt-get update failed; using the cached package lists"
        sudo apt-get install -y $APT_PKGS \
            || warn "apt-get could not install every dependency; continuing."
    else
        warn "no dnf/apt-get -- install kernel build deps yourself, then re-run with INSTALL_DEPS=0"
    fi
fi

say "Preflight"
for t in make gcc flex bison bc rsync cpio; do command -v "$t" >/dev/null || die "missing: $t"; done
[ "$(id -u)" -ne 0 ] || die "do not run this whole script as root.
       The build runs as you; only the install steps use sudo."

# ------------------------------------------------------------------ GPU detect
#
# VM/legacy drivers are ALWAYS enabled. They are a few hundred KB of modules and
# they are the difference between "this kernel boots on my machine" and "this
# kernel boots anywhere" -- a VM, a server with an ASPEED BMC, a rescue boot on
# unfamiliar hardware. There is no reason to leave them out.

W_AMD=0; W_NV=0; W_INTEL=0
if command -v lspci >/dev/null; then
    while IFS= read -r line; do
        case "$line" in
            *[Aa][Mm][Dd]*|*ATI*) W_AMD=1;   printf '  found: %s\n' "$line" ;;
            *NVIDIA*|*nVidia*)    W_NV=1;    printf '  found: %s\n' "$line" ;;
            *Intel*)              W_INTEL=1; printf '  found: %s\n' "$line" ;;
        esac
    done < <(lspci -nn 2>/dev/null | grep -Ei 'vga|3d|display' || true)
fi

case "$GPU" in
  auto)
    if [ "$W_AMD$W_NV$W_INTEL" = "000" ]; then
        warn "no AMD/NVIDIA/Intel GPU detected (headless, VM, or unknown hardware).
         Enabling all vendors so the kernel boots anywhere. Use GPU= to narrow it."
        W_AMD=1; W_NV=1; W_INTEL=1
    fi ;;
  all) W_AMD=1; W_NV=1; W_INTEL=1 ;;
  *)
    W_AMD=0; W_NV=0; W_INTEL=0
    IFS=',' read -ra want <<< "$GPU"
    for v in "${want[@]}"; do case "${v// /}" in
        amd) W_AMD=1 ;; nvidia|nv) W_NV=1 ;; intel) W_INTEL=1 ;; vm|none) ;;
        *) die "unknown GPU vendor '$v' (use: auto all amd nvidia intel vm)" ;;
    esac; done ;;
esac
printf '  enabling  : %s%s%s%s\n' \
    "$([ "$W_AMD" = 1 ] && echo 'AMD ')" "$([ "$W_NV" = 1 ] && echo 'NVIDIA ')" \
    "$([ "$W_INTEL" = 1 ] && echo 'Intel ')" 'VM/legacy(always)'

MAKE_ARGS=(-j"$JOBS")
if [ "$TOOLCHAIN" = llvm ]; then
    [ -n "$LLVM_PREFIX" ] && case "$LLVM_PREFIX" in */) ;; *) die "LLVM_PREFIX must end in a slash -- kbuild treats it as a path prefix";; esac
    MAKE_ARGS+=(LLVM="${LLVM_PREFIX:-1}")
    # tools/lib/bpf can be compiled by gcc (non-PIC) while the host link runs
    # clang/ld.lld, which defaults to PIE. That mix fails building
    # tools/bpf/resolve_btfids with R_X86_64_32 relocation errors.
    MAKE_ARGS+=(HOSTLDFLAGS=-no-pie)
    printf '  toolchain : clang (%s)\n' "${LLVM_PREFIX:-from PATH}"
else
    printf '  toolchain : %s\n' "$(gcc --version | head -1)"
fi

# ---------------------------------------------------------------------- source

if [ -z "$SRC" ]; then
    if [ -z "$KVER" ]; then
        KVER=$(curl -fsSL https://www.kernel.org/finger_banner 2>/dev/null \
            | awk '/latest stable/{print $NF; exit}') || true
        [ -n "$KVER" ] || die "could not determine the latest kernel version; set KVER="
    fi
    SRC="$WORK/linux-$KVER"
    if [ ! -d "$SRC" ]; then
        mkdir -p "$WORK"; cd "$WORK"
        say "Downloading linux-$KVER"
        curl -fL --progress-bar -O "https://cdn.kernel.org/pub/linux/kernel/v${KVER%%.*}.x/linux-$KVER.tar.xz"
        tar xf "linux-$KVER.tar.xz"
    fi
fi
[ -f "$SRC/Makefile" ] || die "no kernel source at $SRC"
cd "$SRC"
# Canonicalise now that we are standing in it, BEFORE it becomes a state key.
# SRC is the [section] name, and a trailing slash from tab-completion, a
# relative "." or a symlinked path would each key the same tree differently --
# and a key that misses reads as "no previous attempt", which under CLEAN=auto
# means mrproper on a tree that was perfectly good.  pwd -P resolves all three.
SRC="$(pwd -P)"
printf '  source    : %s (%s)\n  jobs      : %s\n' "$SRC" "$(make -s kernelversion)" "$JOBS"

# ------------------------------------------------------- previous build attempt

state_init
state_load
trap _state_on_exit EXIT

say "Previous build attempt"
prev_status="${STATE[status]:-}"
# -print -quit stops at the first hit: on a fully built tree this answers in
# milliseconds, where counting all ~16000 objects would walk 4.6G of tree.
objects="$(find . -name '*.o' -not -path './scripts/*' -print -quit 2>/dev/null || true)"

if [ -n "$prev_status" ]; then
    printf '  recorded  : %s' "$prev_status"
    [ -n "${STATE[finished]:-}" ] && printf ' (%s)' "${STATE[finished]}"
    [ "$prev_status" = failed ] && printf ' at stage %s' "${STATE[stage]:-unknown}"
    printf '\n'
else
    printf '  recorded  : nothing -- no entry for this tree in %s\n' "$STATE_FILE"
fi

do_clean=0; clean_why=""
case "$CLEAN" in
  never)  clean_why="CLEAN=never" ;;
  always) do_clean=1; clean_why="CLEAN=always" ;;
  auto)
    if [ -z "$objects" ]; then
        clean_why="no object files present -- nothing to clean"
    else
        case "$prev_status" in
          built|installed) clean_why="the previous run finished ($prev_status); rebuilding incrementally is correct and far faster" ;;
          building)        do_clean=1; clean_why="a previous run started and never recorded finishing -- interrupted or killed" ;;
          failed)          do_clean=1; clean_why="the previous run failed at stage ${STATE[stage]:-unknown}" ;;
          "")              do_clean=1; clean_why="objects are present but nothing recorded how they got there" ;;
          *)               do_clean=1; clean_why="recorded status '$prev_status' is not a completed build" ;;
        esac
    fi ;;
  *) die "CLEAN must be auto, always or never (got '$CLEAN')" ;;
esac

# Back up BEFORE announcing or doing anything, and re-test afterwards.  If the
# configuration cannot be saved then cleaning would destroy it outright, and not
# cleaning is always the safer of the two -- an incremental build on a suspect
# tree is a slow problem, a deleted config is an unrecoverable one.
if [ "$do_clean" = 1 ]; then
    _bk=""
    if [ -f .config ]; then
        # The FIRST backup is the valuable one: by the second cleaning run
        # .config may no longer be the user's file, so overwriting a fixed name
        # would replace the only copy of their original.  Keep the first,
        # timestamp the rest.  --remove-destination because a plain `cp -a` onto
        # an existing symlink writes THROUGH it, clobbering its target.
        _bk=.config.before-clean
        [ -e "$_bk" ] && _bk=".config.before-clean.$(date +%Y%m%d-%H%M%S 2>/dev/null || echo prev)"
        if cp -a --remove-destination .config "$_bk" 2>/dev/null; then
            printf '  saved     : .config -> %s\n' "$_bk"
        else
            do_clean=0; _bk=""
            clean_why="could not save .config, so cleaning would destroy it"
            warn "cannot back up $SRC/.config -- NOT cleaning this tree.
       Fix its permissions, or move it aside yourself, or pass CLEAN=never to
       stop being asked.  Building incrementally instead."
        fi
    fi
fi

# A SECOND test, not an else: $do_clean can have been cleared just above, and
# the previous version of this block set it there and then cleaned anyway --
# printing "not cleaning" immediately before deleting the file it could not save.
if [ "$do_clean" = 1 ]; then
    printf '  cleaning  : %s\n' "$clean_why"
    # mrproper, not clean: the failure this recovers from is a tree left
    # inconsistent, and `make clean` keeps include/config and the generated
    # headers that are the usual culprits.  It deletes .config too, which is
    # incidental -- cleaning is about build PRODUCTS, not your configuration --
    # so that is restored immediately afterwards.
    STATE_STAGE=clean
    make -s mrproper
    printf '  cleaned   : %s\n' "$SRC"
    # Only for CONFIG_BASE=auto: the other modes are an explicit instruction
    # about where the config comes from, and restoring one would contradict it.
    if [ "$CONFIG_BASE" = auto ] && [ -n "$_bk" ] && [ -f "$_bk" ]; then
        cp -a --remove-destination "$_bk" .config \
            && printf '  restored  : .config kept across the clean (from %s)\n' "$_bk"
    fi
else
    printf '  keeping   : %s\n' "$clean_why"
fi

state_set tree      "$SRC"
state_set version   "$(make -s kernelversion)"
state_set toolchain "$TOOLCHAIN"
state_set jobs      "$JOBS"
state_set log       "$BUILD_LOG"
state_set started   "$(date -Is 2>/dev/null || date 2>/dev/null || true)"
state_set status    configuring
state_set stage     ""
state_set exit_code ""
state_set finished  ""      # or the previous run's finish time reads as this one's
STATE_STAGE=configure

# --------------------------------------------------------------------- configure

say "Configuring"

# Copy the running distro's config in.  Factored out because CONFIG_BASE=auto
# calls it only as a fallback, where the old code called it unconditionally and
# so destroyed a tuned .config on every single run.
# Never write .config in place.  A redirect truncates the target before the
# first byte arrives, so a corrupt source or a killed run leaves a FRAGMENT --
# and under CONFIG_BASE=auto the next run would adopt that fragment as its base,
# with olddefconfig silently defaulting every symbol the fragment never reached.
# Writing to a temp and renaming makes the swap atomic; rename also replaces a
# .config SYMLINK rather than writing through it to whatever it points at.
install_config() {
    local src="$1" tmp
    tmp=".config.seed.$$"
    case "$src" in
      *.gz) zcat "$src" > "$tmp" ;;
      *)    cp    "$src"   "$tmp" ;;
    esac || { rm -f "$tmp"; die "could not read the config at $src"; }
    [ -s "$tmp" ] || { rm -f "$tmp"; die "the config at $src is empty"; }
    mv -f "$tmp" .config
}

seed_from_distro() {
    local c base=""
    for c in "/boot/config-$(uname -r)" /proc/config.gz; do
        [ -r "$c" ] && { base="$c"; break; }
    done
    if [ -n "$base" ]; then
        printf '  base: %s\n' "$base"
        install_config "$base"
    else
        warn "no distro config found; starting from defconfig -- review before installing"
        make defconfig >/dev/null
    fi
}

case "$CONFIG_BASE" in
  auto)
      # Cheap sanity gate before adopting it: a truncated or empty file would
      # otherwise be taken as authoritative and quietly defaulted by
      # olddefconfig into a config nobody chose.
      if [ -s .config ] && grep -q '^CONFIG_[A-Z0-9_]*=' .config 2>/dev/null; then
          printf '  base: the .config already in this tree -- kept, not replaced\n'
          printf '        (CONFIG_BASE=distro to reseed from the running kernel)\n'
      else
          [ -e .config ] && warn "$SRC/.config is empty or has no CONFIG_ lines -- ignoring it
       and seeding from the running kernel instead.  The old file is kept as
       .config.rejected if you want to look at it."
          [ -e .config ] && mv -f .config .config.rejected
          seed_from_distro
      fi ;;
  distro)
      seed_from_distro ;;
  defconfig)
      printf '  base: make defconfig\n'
      make defconfig >/dev/null ;;
  *)
      [ -r "$CONFIG_BASE" ] || die "CONFIG_BASE=$CONFIG_BASE is not a readable file.
       Expected: auto, distro, defconfig, or a path to a kernel config."
      printf '  base: %s\n' "$CONFIG_BASE"
      install_config "$CONFIG_BASE" ;;
esac

# Bring whatever base was chosen up to date with THIS tree's Kconfig before the
# enforcement below.  A config from another kernel version is missing symbols
# this one has; without this, scripts/config sets options against a stale symbol
# table and the second olddefconfig quietly drops them again.
make olddefconfig >/dev/null
state_set config_base "$CONFIG_BASE"

cfg() { ./scripts/config "$@"; }

# --- shared GPU-compute plumbing ---------------------------------------------
# These are DEPENDENCIES, not preferences. HSA_AMD_SVM depends on DEVICE_PRIVATE
# and HSA_AMD_P2P depends on PCI_P2PDMA; with those off, asking for them
# silently does nothing and olddefconfig quietly drops them. The same machinery
# backs NVIDIA UVM/GPUDirect and Intel's shared virtual memory.
cfg --enable ZONE_DEVICE
cfg --enable DEVICE_PRIVATE          # unaddressable device memory (HMM)
cfg --enable PCI_P2PDMA              # peer-to-peer DMA between GPUs over PCIe
cfg --enable HMM_MIRROR
cfg --enable MMU_NOTIFIER
cfg --enable DMA_SHARED_BUFFER
cfg --enable DRM_ACCEL               # the accel subsystem both NPUs live under

# --- always: a console before any GPU driver, and the VM/BMC drivers ---------
cfg --enable SYSFB_SIMPLEFB
cfg --module DRM_SIMPLEDRM           # EFI/VESA framebuffer -- your rescue console
cfg --module DRM_VIRTIO_GPU          # QEMU/KVM, virt-manager, most Linux VMs
cfg --module DRM_VMWGFX              # VMware
cfg --module DRM_QXL                 # SPICE
cfg --module DRM_BOCHS               # QEMU -vga std
cfg --module DRM_AST                 # ASPEED BMC -- almost every rack server
cfg --module DRM_HYPERV              # Hyper-V / WSL2
cfg --module DRM_UDL                 # DisplayLink USB adapters

if [ "$W_AMD" = 1 ]; then
    # --- AMD: display, ROCm compute, and the Ryzen AI NPU --------------------
    cfg --module DRM_AMDGPU              # MODULE, not built-in -- see the header
    cfg --enable  DRM_AMD_DC             # modern display stack: atomic KMS, DP/HDMI audio
    cfg --enable  DRM_AMDGPU_USERPTR     # required by ROCm and by GL/Vulkan interop
    cfg --enable  DRM_AMDGPU_SI          # Southern Islands (older GCN)
    cfg --enable  DRM_AMDGPU_CIK         # Sea Islands
    cfg --enable  HSA_AMD                # KFD -- creates /dev/kfd, ROCm's entry point
    cfg --enable  HSA_AMD_SVM            # shared virtual memory: HIP managed memory
    cfg --enable  HSA_AMD_P2P            # direct GPU-to-GPU transfers
    cfg --enable  AMD_IOMMU              # DRM_ACCEL_AMDXDNA depends on it
    cfg --module  DRM_ACCEL_AMDXDNA      # "AMD AI Engine" NPU, Ryzen AI 300+
fi

if [ "$W_NV" = 1 ]; then
    # --- NVIDIA --------------------------------------------------------------
    # nouveau is the in-tree open driver. Build it as a MODULE even if you intend
    # to use NVIDIA's proprietary or open-kernel modules: those require nouveau
    # to be absent at load time, and you can only blacklist a module.
    #
    # NVIDIA's own modules are out-of-tree and built by DKMS against
    # /lib/modules/$KREL/build, which `make modules_install` creates. Nothing
    # else in the config is needed for them -- except that an unsigned
    # out-of-tree module must be loadable, hence MODULE_SIG_FORCE off below.
    #
    # NOVA (the Rust GSP driver for Turing and later) is deliberately NOT
    # enabled: it depends on CONFIG_RUST and a matching rustc/bindgen, and is
    # still early. Enable RUST first, then NOVA_CORE and DRM_NOVA.
    cfg --module DRM_NOUVEAU
fi

if [ "$W_INTEL" = 1 ]; then
    # --- Intel: graphics, compute, and the Meteor Lake+ NPU ------------------
    # i915 and xe overlap on Tiger Lake .. Meteor Lake. Building BOTH as modules
    # is correct and safe: only one binds a given device, chosen by the driver's
    # own device table and overridable with i915.force_probe / xe.force_probe.
    cfg --module DRM_I915                # Gen2 .. Gen12: everything up to Alder/Raptor Lake
    cfg --module DRM_XE                  # Xe: Arc, Lunar Lake, Battlemage, newer iGPUs
    cfg --module DRM_ACCEL_IVPU          # "Intel NPU", Meteor Lake (14th gen) and newer
fi

# --- things that break a copied distro config --------------------------------
# Distro configs point at signing keys and revocation lists shipped in their own
# source package. Those files are not in the upstream tarball and the build dies
# late with a confusing openssl error.
cfg --set-str SYSTEM_TRUSTED_KEYS ""
cfg --set-str SYSTEM_REVOCATION_KEYS ""
# Same shape, one kernel release later: distro configs point EFI_SBAT_FILE at
# kernel.sbat, which also lives only in their kernel SOURCE package.  The rule
# in arch/x86/boot/compressed/Makefile is a bare file dependency --
#     $(obj)/sbat.o: $(CONFIG_EFI_SBAT_FILE)
# -- so a missing file is not a warning but a hard stop before a single object
# of the boot stub links:
#     No rule to make target 'kernel.sbat', needed by '.../compressed/sbat.o'.
# EFI_SBAT is `def_bool y if EFI_SBAT_FILE!=""`, so it cannot be switched off
# directly; clearing the string is what turns it off.  Nothing is lost -- SBAT
# generations are Secure Boot revocation policy owned by whoever holds the
# signing certificate, and upstream defines no components at all.
cfg --set-str EFI_SBAT_FILE ""
cfg --disable EFI_SBAT
cfg --disable MODULE_SIG_FORCE       # or no out-of-tree GPU module will load
# Distro configs set CONFIG_WERROR=y, which is reasonable for the exact kernel
# and exact compiler that distro tests -- and wrong for every other pairing.
# This script deliberately feeds a distro config into a DIFFERENT kernel
# version, and often with a different compiler; the new warnings that follow
# are normal, and with WERROR they are all fatal, hours into the build.
cfg --disable WERROR

# The AMD display DML files (display_mode_vba_31 / _314 and friends) build ~2.4K
# frames doing floating-point mode validation, so a distro config's 2048 warns on
# every single build.  Raised here so the setting survives -- .config is copied
# fresh from /boot/config-$(uname -r) on every run, which silently reverts a
# hand-edit.  Note what this does NOT do: -Wframe-larger-than is a diagnostic
# threshold, not an allocation.  No function gets a byte more room, vmlinux is
# unchanged, and the real bound is THREAD_SIZE (16K on x86_64, set by
# THREAD_SIZE_ORDER in arch/x86/include/asm/page_64_types.h).  It is consumed
# once, globally, at scripts/Makefile.warn:25.  FRAME_WARN=3072 keeps the check
# useful while still covering the DML files; FRAME_WARN=0 turns it off entirely.
cfg --set-val FRAME_WARN "$FRAME_WARN"

if [ "$KEEP_DEBUG_INFO" != 1 ]; then
    # Full DWARF makes the build far slower and modules roughly 10x bigger. Set
    # KEEP_DEBUG_INFO=1 if you need BTF, bpftrace, or kernel debugging.
    cfg --disable DEBUG_INFO_BTF
    cfg --disable DEBUG_INFO_BTF_MODULES
    # The DWARF-version choice has to be cleared BEFORE selecting NONE, and the
    # order is not cosmetic.  scripts/config edits in place, so enabling
    # DEBUG_INFO_NONE only rewrites the '# CONFIG_DEBUG_INFO_NONE is not set'
    # line where it already sits -- which in a distro config is the line ABOVE
    # CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y.  Kconfig resolves a choice by
    # "the last one read wins" (confdata.c), so the DWARF entry won, selected
    # DEBUG_INFO, and full debug info stayed on for every module -- silently,
    # because nothing below checked for it.
    cfg --disable DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT
    cfg --disable DEBUG_INFO_DWARF4
    cfg --disable DEBUG_INFO_DWARF5
    cfg --enable  DEBUG_INFO_NONE
fi

cfg --set-str LOCALVERSION "$LOCALVERSION"
cfg --disable LOCALVERSION_AUTO
make olddefconfig >/dev/null

# --------------------------------------------------------------------- verify

say "Verifying the configuration"
fail=0
check() {
    got=$(grep -E "^(# )?CONFIG_$1[ =]" .config | head -1)
    if [ "$got" = "CONFIG_$1=$2" ]; then printf '  ok   CONFIG_%-24s = %s\n' "$1" "$2"
    else printf '  FAIL CONFIG_%-24s want %s, got: %s\n' "$1" "$2" "${got:-<absent>}"; fail=1; fi
}
note() {  # informational: hardware- or arch-gated, absence is not an error
    got=$(grep -E "^(# )?CONFIG_$1[ =]" .config | head -1)
    printf '  --   CONFIG_%-24s %s\n' "$1" "${got:-<absent>}"
}
check DEVICE_PRIVATE y
check PCI_P2PDMA y
check DRM_SIMPLEDRM m
check DRM_VIRTIO_GPU m
# Verified, not assumed: this one failed silently for a long time, and the only
# symptom was a build that took far longer and modules ten times the size.
[ "$KEEP_DEBUG_INFO" = 1 ] || check DEBUG_INFO_NONE y
if [ "$W_AMD" = 1 ]; then
    check DRM_AMDGPU m; check DRM_AMD_DC y; check DRM_AMDGPU_USERPTR y
    check HSA_AMD y;    check HSA_AMD_SVM y; check HSA_AMD_P2P y
    note  DRM_ACCEL_AMDXDNA          # x86_64 + AMD_IOMMU only
fi
[ "$W_NV" = 1 ]    && check DRM_NOUVEAU m
if [ "$W_INTEL" = 1 ]; then
    check DRM_I915 m
    note  DRM_XE                     # newer trees only
    note  DRM_ACCEL_IVPU             # x86_64 only
fi
[ "$fail" -eq 0 ] || die "the config did not come out as intended -- do NOT install this kernel.
       Each FAIL is a dependency that is off, not a typo. Run 'make menuconfig'
       and search (/) for the symbol to see what it needs."

KREL=$(make -s kernelrelease)
printf '  kernel release: %s\n' "$KREL"
state_set release "$KREL"
state_set modules "/lib/modules/$KREL"

# ------------------------------------------------------------------------ build

say "Building -- this takes a while. Use tmux/screen over ssh."
STATE_STAGE=build
state_set status building
# The previous log is kept as .prev rather than appended to, so the file cannot
# grow without bound across runs.  pipefail (set at the top) is what makes the
# tee honest: without it the pipeline would report tee's exit status and a
# failed build would look like a successful one.
if [ -f "$BUILD_LOG" ]; then mv -f "$BUILD_LOG" "$BUILD_LOG.prev" 2>/dev/null || true; fi
if { : > "$BUILD_LOG"; } 2>/dev/null; then    # braces: else the error escapes fd2
    printf '  logging to: %s\n' "$BUILD_LOG"
    make "${MAKE_ARGS[@]}" 2>&1 | tee "$BUILD_LOG"
else
    warn "cannot write $BUILD_LOG -- building without a log.  Set BUILD_LOG=<path>."
    make "${MAKE_ARGS[@]}"
fi
state_set status built
state_set finished "$(date -Is 2>/dev/null || date 2>/dev/null || true)"

[ "$INSTALL" = 1 ] || {
    say "Built, not installed."
    printf '  Re-run with INSTALL=1, or install by hand:\n'
    printf '      sudo make %s modules_install\n      sudo make %s install\n' "${MAKE_ARGS[*]}" "${MAKE_ARGS[*]}"
    exit 0
}

# ---------------------------------------------------------------------- install

if command -v dkms >/dev/null && dkms status 2>/dev/null | grep -q '^amdgpu'; then
    warn "amdgpu-dkms is installed. It exists to backport amdgpu to OLD enterprise
         kernels; the in-tree driver you just built is newer. It will try, and
         fail, to build against $KREL. To stop it without breaking your distro
         kernel, cap it:
             sudo sh -c 'echo BUILD_EXCLUSIVE_KERNEL_MAX=\"$(uname -r | cut -d- -f1)\" >> /usr/src/amdgpu-*/dkms.conf'"
fi
if command -v dkms >/dev/null && dkms status 2>/dev/null | grep -q '^nvidia'; then
    printf '  note: nvidia-dkms will rebuild for %s automatically once\n' "$KREL"
    printf '        modules_install creates /lib/modules/%s/build.\n' "$KREL"
    printf '        Remember to blacklist nouveau if you use the proprietary driver.\n'
fi

say "Installing modules and kernel (sudo)"
STATE_STAGE=install
state_set status installing
sudo make "${MAKE_ARGS[@]}" modules_install
sudo make "${MAKE_ARGS[@]}" install
state_set status installed
state_set finished "$(date -Is 2>/dev/null || date 2>/dev/null || true)"

say "Post-install checks"
img=""
for i in "/boot/initramfs-$KREL.img" "/boot/initrd.img-$KREL"; do [ -f "$i" ] && img="$i"; done
if [ -n "$img" ]; then
    lsi=""; command -v lsinitrd >/dev/null && lsi=lsinitrd
    [ -z "$lsi" ] && command -v lsinitramfs >/dev/null && lsi=lsinitramfs
    if [ -n "$lsi" ]; then
        for fw in amdgpu nvidia i915 xe; do
            [ "$fw" = amdgpu ] && [ "$W_AMD"   != 1 ] && continue
            [ "$fw" = nvidia ] && [ "$W_NV"    != 1 ] && continue
            { [ "$fw" = i915 ] || [ "$fw" = xe ]; } && [ "$W_INTEL" != 1 ] && continue
            n=$(sudo "$lsi" "$img" 2>/dev/null | grep -c "firmware/$fw" || true)
            [ "${n:-0}" -gt 0 ] && printf '  ok   %s %s firmware files in the initramfs\n' "$n" "$fw" \
                                || warn "no $fw firmware in $img -- install linux-firmware and
         regenerate the initramfs BEFORE rebooting, or that GPU will not come up."
        done
    fi
fi
[ -e "/lib/modules/$KREL/build" ] \
    && printf '  ok   /lib/modules/%s/build present -- DKMS and out-of-tree modules can build\n' "$KREL" \
    || warn "/lib/modules/$KREL/build is missing; DKMS modules cannot build against this kernel"
# Driver presence check.
#
# NOTE ON SPELLING: these are FILE names, and kernel module files keep their
# hyphens -- virtio-gpu.ko, snd-hda-intel.ko, xen-blkfront.ko.  Nearly 40% of
# the modules in a typical kernel have a hyphen.  It is the module's internal
# NAME that uses underscores, which is what lsmod, modinfo -F name, /proc/modules
# and /sys/module/ all display.  Since this searches with find -name, the hyphen
# is correct; "fixing" virtio-gpu to virtio_gpu here finds nothing.
#
# A driver absent from /lib/modules is not necessarily a failure -- it may be
# built in (=y), which is a perfectly good outcome and produces no .ko at all.
# The old version of this loop printed nothing in that case, so a driver that
# genuinely failed to build looked identical to one that succeeded as built-in.
# Consult .config to tell those two apart.
for entry in "amdgpu:DRM_AMDGPU" "nouveau:DRM_NOUVEAU" "i915:DRM_I915" \
             "xe:DRM_XE" "simpledrm:DRM_SIMPLEDRM" "virtio-gpu:DRM_VIRTIO_GPU"; do
    m="${entry%%:*}"; sym="${entry#*:}"
    if find "/lib/modules/$KREL" -name "$m.ko*" 2>/dev/null | head -1 | grep -q .; then
        printf '  ok   %-12s installed as a module (%s.ko)\n' "$m" "$m"
    elif grep -q "^CONFIG_$sym=y" .config 2>/dev/null; then
        printf '  ok   %-12s built into the kernel image (CONFIG_%s=y)\n' "$m" "$sym"
    elif grep -q "^CONFIG_$sym=m" .config 2>/dev/null; then
        warn "$m: CONFIG_$sym=m was requested but no $m.ko was installed.
       That is a real failure -- check the modules_install step."
    else
        printf '  --   %-12s not enabled in this config\n' "$m"
    fi
done

say "Done. Keep your current kernel in the boot menu until $KREL is proven."