[{"content":"Just for personal note, haven\u0026rsquo;t test on others.\nKernel Config 1 make defconfig CC=clang if want to include other module which is not enable be default in defconfig. run for e.g.\n1 2 3 4 5 ./scripts/config --module CONFIG_MAC80211_HWSIM ./scripts/config --module CONFIG_IWLWIFI=y ./scripts/config --module CONFIG_IWLWIFI_LEDS=y ./scripts/config --module CONFIG_IWLDVM=m ./scripts/config --module CONFIG_IWLMVM=m build-kernel.sh 1 2 3 4 5 6 7 #!/bin/bash set -e make CC=clang -j$(nproc) python3 scripts/clang-tools/gen_compile_commands.py echo \u0026#34;Done — bzImage ready at arch/x86/boot/bzImage\u0026#34; For subsequent small changes just run make CC=clang -j$(nproc) directly. Make only recompiles changed files, so a single .c change takes a few seconds.\nRaw Kernel + Busybox Initramfs QEMU loads the kernel and a cpio archive, the kernel unpacks it into a tmpfs, runs /init, and drops you into a busybox shell.\nbuild-initramfs.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #!/bin/bash set -e KBUILD=$(pwd) rm -rf initramfs mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev,lib/modules} cp /usr/bin/busybox initramfs/bin/ cd initramfs/bin ./busybox --install . cd \u0026#34;$KBUILD\u0026#34; cat \u0026gt; initramfs/init \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev 2\u0026gt;/dev/null || mdev -s echo \u0026#34;Kernel $(uname -r) booted!\u0026#34; exec /bin/sh EOF chmod +x initramfs/init cd initramfs find . | cpio -o -H newc | gzip \u0026gt; ../initramfs.cpio.gz cd .. echo \u0026#34;Done — initramfs.cpio.gz ready\u0026#34; boot.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash set -e KBUILD=$(pwd) qemu-system-x86_64 \\ -kernel \u0026#34;$KBUILD/arch/x86/boot/bzImage\u0026#34; \\ -initrd \u0026#34;$KBUILD/initramfs.cpio.gz\u0026#34; \\ -append \u0026#34;console=ttyS0,115200 earlyprintk=serial,ttyS0,115200 rdinit=/init nokaslr\u0026#34; \\ -nographic \\ -serial mon:stdio \\ -device e1000,netdev=net0 \\ -netdev user,id=net0,hostfwd=tcp::2222-:22 \\ -m 512M \\ -no-reboot Rebuild 1 2 3 bash build-kernel.sh bash build-initramfs.sh bash boot.sh Exit QEMU with Ctrl-A X or run shutdown.\nFull Arch Linux Rootfs When you need pacman, ssh, or any real tooling, install Arch onto a qcow2 image and boot your custom kernel directly into it.\nCreate the Disk 1 qemu-img create -f qcow2 rootfs.qcow2 10G Install Arch from ISO 1 2 3 4 5 6 7 8 9 10 qemu-system-x86_64 \\ -cdrom archlinux-x86_64.iso \\ -boot d \\ -drive file=rootfs.qcow2,format=qcow2 \\ -device e1000,netdev=net0 \\ -netdev user,id=net0,hostfwd=tcp::2222-:22 \\ -m 2G \\ -enable-kvm \\ -no-reboot \\ -vnc :0 Connect with vncviewer localhost:5900. Inside the installer:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 fdisk /dev/sda \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; g n 1 w EOF mkfs.ext4 /dev/sda1 mount /dev/sda1 /mnt # base only, no need linux package pacstrap /mnt base genfstab -U /mnt \u0026gt;\u0026gt; /mnt/etc/fstab arch-chroot /mnt passwd root # Network (optional) cat \u0026gt; /etc/systemd/network/20-wired.network \u0026lt;\u0026lt; \u0026#39;NET\u0026#39; [Match] Name=ens3 [Network] DHCP=yes NET systemctl enable systemd-networkd systemd-resolved exit umount -R /mnt poweroff boot.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/bash set -e KBUILD=$(pwd) qemu-system-x86_64 \\ -kernel \u0026#34;$KBUILD/arch/x86/boot/bzImage\u0026#34; \\ -drive file=\u0026#34;$KBUILD/rootfs.qcow2\u0026#34;,format=qcow2 \\ -append \u0026#34;console=ttyS0,115200 earlyprintk=serial,ttyS0,115200 root=/dev/sda1 rw nokaslr\u0026#34; \\ -nographic \\ -serial mon:stdio \\ -device e1000,netdev=net0 \\ -netdev user,id=net0,hostfwd=tcp::2222-:22 \\ -m 2G \\ -enable-kvm \\ -no-reboot SSH in with:\n📝 Note Need to install and start openssh, and set PermitRootLogin = yes in /etc/ssh/ssh_config.\n1 ssh -p 2222 root@127.0.0.1 Rebuild 1 2 3 bash build-kernel.sh bash build-initramfs.sh bash boot.sh Exit QEMU with Ctrl-A X or run shutdown.\n","date":"2026-04-08T16:10:31-07:00","permalink":"/blog/p/build-and-run-on-kernel-using-qemu/","title":"Build and Run on kernel using QEMU"},{"content":" Discord Rich Presence daemon for Visual Novels on Linux. Detects games launched via Lutris or Steam, looks them up on VNDB, and shows the title, cover art, and total playtime in your Discord status.\nVoice channel User activity Features Detects games from Lutris (exact name from wrapper) and Steam (AppID → ACF → store name) Fuzzy title matching against VNDB using trigram similarity + full-width character normalisation Fallback: searches the release endpoint and follows the relation back to the parent VN Suppresses explicit cover images (sexual ≥ 1.80 or violence ≥ 1.80) — title still shows Playtime from Lutris DB (pga.db) or Steam VDF (localconfig.vdf) — no API key needed Discord elapsed timer reflects total hours played, not just this session Editable cache.csv for aliases, hard-links, and SKIP entries — reloaded live while running ignore.txt with exact-match for short entries (\u0026lt; 4 chars) to prevent false positives like sh How it works Main loop Every 5 seconds the daemon runs detection, debounces the result, checks the cache, queries VNDB if needed, and updates Discord.\nflowchart LR A[Detection\\nLutris or Steam] --\u003e B[Debounce\\nstable 2 polls] B --\u003e C{Cache\\nlookup} C -- hit + fresh --\u003e E[Discord RPC] C -- hit + expired --\u003e D[VNDB search] C -- miss --\u003e D D --\u003e E Detection flowchart TD P[Scan /proc every 5s] P --\u003e L{lutris-wrapper\\nin cmdline?} L -- yes --\u003e LN[Game name from\\nwrapper args] L -- no --\u003e S{SteamAppId\\nin environ?} S -- yes --\u003e SA[Read appmanifest_ID.acf\\nfrom all library paths] SA --\u003e SN[Steam store name] S -- no --\u003e NONE[Nothing detected] LN --\u003e ST[Read starttime\\nfrom /proc/pid/stat field 22] SN --\u003e ST ST --\u003e SORT[Sort candidates\\nby starttime ascending]Lutris passes the game name explicitly as command-line arguments after lutris-wrapper, so the name is always exact. Steam injects SteamAppId as an environment variable into every game process — the daemon reads it from /proc/\u0026lt;pid\u0026gt;/environ, then finds the matching .acf file across all Steam library paths (including custom drives read from libraryfolders.vdf).\nAfter all candidates are collected, the daemon reads field 22 (starttime) from each process\u0026rsquo;s /proc/\u0026lt;pid\u0026gt;/stat. Candidates are then sorted ascending by starttime, so the process that launched first is always tried first during VNDB resolution. This makes multi-candidate priority deterministic and reproducible across polls.\n📝 Note See the proc/[PID]/stat field reference for a full breakdown of all stat fields.\n💡 Tip Run with --verbose to see candidates list in the debug output:\n1 2 3 4 5 [DEBUG] src/main.cpp:73 4 game candidate(s) found: [DEBUG] src/main.cpp:75 [lutris] pid=2037324 name=\u0026#34;終ノ空 remake\u0026#34; starttime(clock ticks)=4906595 [DEBUG] src/main.cpp:75 [lutris] pid=2086738 name=\u0026#34;X-Plane 12\u0026#34; starttime(clock ticks)=4906701 [DEBUG] src/main.cpp:75 [steam-appid] pid=2037822 name=\u0026#34;心象天儀本線 ~Per aspera ad astra~ Demo\u0026#34; starttime(clock ticks)=4906857 [DEBUG] src/main.cpp:75 [lutris] pid=2038526 name=\u0026#34;サクラノ詩－櫻の森の上を舞う－\u0026#34; starttime(clock ticks)=4907260 VNDB resolution flowchart TD Q[Search query] --\u003e VN[POST /kana/vn] VN --\u003e SIM{Trigram similarity\\n≥ 0.35 OR substring match?} SIM -- yes --\u003e MATCH[VnInfo returned] SIM -- no --\u003e REL[POST /kana/release] REL --\u003e FOUND{Release found?} FOUND -- yes --\u003e ID[Fetch parent VN\\nby exact ID] ID --\u003e MATCH FOUND -- no --\u003e NOMATCH[No match\\nclear presence]Before the similarity check, full-width ASCII (！２→!2, （→() is normalised to half-width so Japanese game names from Lutris match VNDB\u0026rsquo;s stored titles. There is also a substring boost — if the query appears inside the returned title, the score is raised to at least 0.6, handling cases where the detected name is a short prefix of a long VNDB title.\nCache TTL VNDB results are stored persistently in cache.csv. On each cache hit the daemon compares the entry\u0026rsquo;s cached_at timestamp against VNDB_CACHE_TTL. If the entry is older than the TTL, it is treated as expired and a fresh VNDB query is issued, updating the stored data and timestamp.\nflowchart TD HIT[Cache hit] --\u003e AGE{age \u003e VNDB_CACHE_TTL?} AGE -- no --\u003e USE[Use cached VnInfo] AGE -- yes --\u003e REQUERY[Re-query VNDB\\nupdate cache.csv] REQUERY --\u003e USEThis means ratings, cover images, and release dates in the cache are automatically refreshed over time without any manual intervention. The log line when a re-query fires looks like:\n1 [INFO] Cache expired for \u0026#34;...\u0026#34; age=1442min/1440min — re-querying VNDB Playtime → Discord elapsed timer flowchart TD SRC{Source?} SRC -- lutris --\u003e LDB[Lutris DB\\npga.db · hours] SRC -- steam-appid --\u003e VDF[Steam VDF\\nlocalconfig.vdf · minutes] LDB --\u003e PT[playtime in seconds] VDF --\u003e PT PT --\u003e TS[start_ts = now − playtime] TS --\u003e DISC[Discord: elapsed timer\\ncounts up from start_ts] Cache file (cache.csv / cache.db) Located at ~/.config/vn-discord-rpc/cache.csv (default) or cache.db when CACHE_USE_DB = true. Reloaded automatically whenever the file changes on disk.\nColumn Purpose key Detected Visual Novels name (the search term) alias Redirect this key to a different search term vndb_id v562, v67, SKIP, or empty title Romanised VNDB title (auto-filled) alt_title Original script title, e.g. Japanese (auto-filled) image_url Cover image URL (auto-filled, blank if explicit(sexual or violence)) image_sexual VNDB sexual rating 0–2 (auto-filled) image_violence VNDB violence rating 0–2 (auto-filled) rating VNDB community rating 0–100 (auto-filled) released Release date (auto-filled) cached_at Unix timestamp of last write (auto-filled) Examples:\n1 2 3 4 5 6 7 8 # Alias — fix wrong or garbled detection Nice boat!,School Days,,,,,,,, # Skip — suppress presence for this title 妹ぱらだいす！２,,SKIP,,,,,,, # Hard-link — bypass VNDB query, point directly to an entry My VN Title,,v67,,,,,,, Ignore list (ignore.txt) Located at ~/.config/vn-discord-rpc/ignore.txt. One entry per line, # for comments. Reloaded automatically while running.\nMatching rules:\nEntry \u0026lt; 4 characters: exact match (case-insensitive) — (ie. prevents sh matching Higurashi) Entry ≥ 4 characters: case-insensitive substring match The default file includes common false-positives: Steam runtimes, Proton, Wine helpers, and launchers.\n⚠️ Warning If a title has no VNDB match and is not in the ignore list, the daemon will not add it to ignore automatically. It will retry the VNDB query on every detection. Add a SKIP entry in cache.csv or an entry in ignore.txt to suppress it permanently.\nConfiguration (src/config.hpp) 🚨 Caution Be caution on changing IMAGE_SEXUAL and IMAGE_VIOLENCE thresholds in src/config.hpp. Those thresholds are set to 1.80 (slightly below VNDB\u0026rsquo;s Explicit level of 2.00) because VNDB image ratings are user-reported and may be underrated by a small margin — the 0.20 buffer ensures borderline explicit covers are still suppressed. Raising the threshold above 2.00 would cause explicit (pornographic) cover art to appear in your Discord status, visible to everyone on your friends list. Displaying explicit content in Discord Rich Presence may violates Discord\u0026rsquo;s Terms of Service and may result in a permanent account ban.\nConstant Default Description DISCORD_APP_ID 1482345564698841189 Discord application ID DISCORD_ACTIVITY_TYPE 0 Activity type: 0=Game, 1=Streaming, 2=Listening, 3=Watching IMAGE_SEXUAL 1.80 Maximum sexual rating before cover is suppressed IMAGE_VIOLENCE 1.80 Maximum violence rating before cover is suppressed IMAGE_VOTECOUNT 5 Minimum vote count before ratings are trusted VNDB_MIN_SIMILARITY 0.35 Minimum trigram score to accept a VNDB match POLL_INTERVAL 5s How often to scan /proc for running processes VNDB_CACHE_TTL 24h How long a cache entry is valid before re-querying VNDB STABLE_TITLE_POLLS 2 Polls a candidate must be stable before acting VNDB_MAX_RESULTS 1 Maximum results per VNDB query (index 0 is used) CACHE_USE_DB false Use SQLite (cache.db) instead of CSV (experimental) RPC_STATE_READING \u0026quot;Reading\u0026quot; Discord state string while a VN is active RPC_STATE_IDLE \u0026quot;Idle\u0026quot; Discord state string while idle RPC_DEFAULT_DETAILS \u0026quot;Playing a Visual Novel\u0026quot; Details line when no VNDB match is found RPC_SMALL_IMG_KEY \u0026quot;vndb_logo\u0026quot; Discord asset key for the small VNDB logo image RPC_SMALL_IMG_TEXT \u0026quot;VNDB\u0026quot; Tooltip text for the small image Architecture The project is composed of nine focused, single-responsibility modules:\nModule Source Files Responsibility Entry point main.cpp Main loop, debounce state machine, candidate resolution pipeline Process scanner process_scanner.cpp/.hpp Iterates /proc, detects Lutris and Steam game processes, reads starttime Steam detector steam_detector.cpp/.hpp Reads SteamAppId env vars, parses ACF manifests and VDF playtime files VNDB client vndb_client.cpp/.hpp HTTP POST to VNDB Kana API, trigram matching, release endpoint fallback VN cache vn_cache.cpp/.hpp Persistent CSV/SQLite cache with alias, SKIP, TTL, and live-reload logic Ignore list ignore_list.cpp/.hpp Live-reloadable process-name suppression list with exact/substring rules RPC manager rpc_manager.cpp/.hpp Discord IPC wrapper with rate limiting, deferred flush, and change detection Config config.hpp All compile-time constants in one place Logger logger.hpp Thread-safe ANSI-coloured logger singleton with LOG_DEBUG/INFO/WARN/ERR macros Lutris DB lutris_db.cpp/.hpp Reads and formats playtime from Lutris\u0026rsquo;s SQLite pga.db Key Design Decisions Deterministic multi-candidate priority — when multiple games are running, candidates are sorted by /proc/\u0026lt;pid\u0026gt;/stat field 22 (starttime, clock ticks since boot). The process that launched earliest is always tried first, making priority stable and reproducible across polls without any user configuration.\nDiscord rate-limit compliance — Discord silently drops SET_ACTIVITY calls faster than ~15 seconds apart. RpcManager tracks the wall-clock time of the last successful push and defers any call that falls within a 16-second window. Deferred updates are flushed on the next runCallbacks() tick, ensuring no update is ever lost.\nNo presence flicker on stable sessions — the early-out in the main loop checks whether the currently displayed VN is still in the candidate set before doing any cache or VNDB work. If it is still running, the loop sleeps immediately, so Discord is never unnecessarily cleared and re-set.\nCache-first, ignore-list-second — VNDB HTTP queries are only issued on a true cache miss or TTL expiry. Candidates that fail VNDB resolution are added to the ignore list so subsequent polls skip them instantly, letting the next candidate be tried without any network round trip.\nLive file reloading without restart — both cache.csv and ignore.txt are checked for mtime changes on every poll using std::filesystem::last_write_time. Edits made while the daemon is running take effect within one poll cycle with no signals or process restart needed.\nExplicit content safety — image ratings are only trusted when backed by at least IMAGE_VOTECOUNT votes. Cover URLs are stripped from cache entries for explicit results so they can never accidentally appear after a cache reload even if thresholds are later changed.\nBuilding 📌 Important Initialise submodules before building — the two header-only dependencies are not downloaded automatically.\n1 git submodule update --init --recursive 1 2 cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build Dependencies Library Purpose discord-presence Discord Rich Presence (modern C++ rewrite) nlohmann/json JSON parsing libcurl HTTP requests to VNDB API libsqlite3 Read Lutris playtime database Usage 💡 Tip Run with --verbose if a title is not being detected or matched — the debug output shows exactly which process was found, what VNDB returned, and the similarity score.\n1 2 3 ./vn-discord-rpc # normal ./vn-discord-rpc --verbose # debug logging ./vn-discord-rpc --help Launch a game through Lutris or Steam, and the daemon will detect it automatically. Press Ctrl+C to quit cleanly.\nFile locations File Path Cache (CSV) ~/.config/vn-discord-rpc/cache.csv Cache (SQLite, experimental) ~/.config/vn-discord-rpc/cache.db Ignore list ~/.config/vn-discord-rpc/ignore.txt Lutris DB ~/.local/share/lutris/pga.db Steam VDF ~/.local/share/Steam/userdata/\u0026lt;id\u0026gt;/config/localconfig.vdf ","date":"2026-03-30T01:27:09-07:00","permalink":"/blog/p/vn-presence/","title":"VN Presence"},{"content":"The /proc/[PID]/stat file exposes process status information. Each field is listed below in order of appearance.\nFields ID Field Description 1 pid Process ID 2 tcomm Filename of the executable 3 state Process state: R running, S sleeping, D uninterruptible sleep, Z zombie, T traced or stopped 4 ppid Process ID of the parent process 5 pgrp Process group ID 6 sid Session ID 7 tty_nr TTY the process uses 8 tty_pgrp Process group ID of the TTY 9 flags Task flags 10 min_flt Number of minor faults 11 cmin_flt Number of minor faults including children 12 maj_flt Number of major faults 13 cmaj_flt Number of major faults including children 14 utime User mode jiffies 15 stime Kernel mode jiffies 16 cutime User mode jiffies including children 17 cstime Kernel mode jiffies including children 18 priority Priority level 19 nice Nice level 20 num_threads Number of threads 21 it_real_value (Obsolete, always 0) 22 start_time Time the process started after system boot 23 vsize Virtual memory size 24 rss Resident set memory size 25 rsslim Current limit in bytes on the RSS 26 start_code Address above which program text can run 27 end_code Address below which program text can run 28 start_stack Address of the start of the main process stack 29 esp Current value of ESP 30 eip Current value of EIP 31 pending Bitmap of pending signals 32 blocked Bitmap of blocked signals 33 sigign Bitmap of ignored signals 34 sigcatch Bitmap of caught signals 35 (placeholder) (Was wchan address — use /proc/[PID]/wchan instead) 36 (placeholder) (Reserved) 37 (placeholder) (Reserved) 38 exit_signal Signal sent to the parent thread on exit 39 task_cpu CPU the task is scheduled on 40 rt_priority Realtime priority 41 policy Scheduling policy (see man sched_setscheduler) 42 blkio_ticks Time spent waiting for block I/O 43 gtime Guest time of the task in jiffies 44 cgtime Guest time of child tasks in jiffies 45 start_data Address above which program data+BSS is placed 46 end_data Address below which program data+BSS is placed 47 start_brk Address above which the heap can be expanded with brk() 48 arg_start Address above which the command line is placed 49 arg_end Address below which the command line is placed 50 env_start Address above which the environment is placed 51 env_end Address below which the environment is placed 52 exit_code Thread exit code as reported by waitpid Example 1 871869 (VN-Presence) S 12198 871869 12198 34830 871869 4194560 1205 0 173 0 46734 8492 0 0 20 0 2 0 10523393 176685056 2837 18446744073709551615 94266814361600 94266814993397 140734575381584 0 0 0 0 0 16386 0 0 0 17 16 0 0 0 0 0 94266815325280 94266815330208 94267110821888 140734575389833 140734575389847 140734575389847 140734575394794 0 Parsed field by field:\nID Field Value Notes 1 pid 871869 Process ID 2 tcomm (VN-Presence) Executable name 3 state S Sleeping (interruptible) 4 ppid 12198 Parent PID 5 pgrp 871869 Process group ID 6 sid 12198 Session ID 7 tty_nr 34830 TTY device number 8 tty_pgrp 871869 Foreground process group of TTY 9 flags 4194560 Task flags 10 min_flt 1205 Minor page faults 11 cmin_flt 0 Minor faults including children 12 maj_flt 173 Major page faults 13 cmaj_flt 0 Major faults including children 14 utime 46734 User-mode CPU time (jiffies) — ~467s at 100 Hz 15 stime 8492 Kernel-mode CPU time (jiffies) — ~84s at 100 Hz 16 cutime 0 Children user-mode jiffies 17 cstime 0 Children kernel-mode jiffies 18 priority 20 Kernel priority 19 nice 0 Nice value (0 = default) 20 num_threads 2 Thread count 21 it_real_value 0 Obsolete, always 0 22 start_time 10523393 Jiffies after boot when process started 23 vsize 176685056 Virtual memory size (168 MiB) 24 rss 2837 Resident set size (pages) — 2837 × 4 KiB ≈ 11 MiB 25 rsslim 18446744073709551615 RSS limit (0xFFFFFFFFFFFFFFFF = unlimited) 26 start_code 94266814361600 Start of executable text segment 27 end_code 94266814993397 End of executable text segment 28 start_stack 140734575381584 Stack start address 29 esp 0 Current stack pointer 30 eip 0 Current instruction pointer 31 pending 0 Pending signals bitmap 32 blocked 0 Blocked signals bitmap 33 sigign 16386 Ignored signals bitmap 34 sigcatch 0 Caught signals bitmap 35 (placeholder) 0 (Was wchan) 36 (placeholder) 0 (Reserved) 37 (placeholder) 0 (Reserved) 38 exit_signal 17 Signal sent to parent on exit (17 = SIGCHLD) 39 task_cpu 16 Last CPU the task ran on 40 rt_priority 0 Realtime priority (0 = not realtime) 41 policy 0 Scheduling policy (0 = SCHED_NORMAL) 42 blkio_ticks 0 Block I/O wait ticks 43 gtime 0 Guest time (jiffies) 44 cgtime 0 Children guest time (jiffies) 45 start_data 94266815325280 Start of data+BSS segment 46 end_data 94266815330208 End of data+BSS segment 47 start_brk 94267110821888 Start of heap 48 arg_start 140734575389833 Start of command-line args 49 arg_end 140734575389847 End of command-line args 50 env_start 140734575389847 Start of environment 51 env_end 140734575394794 End of environment 52 exit_code 0 Exit code Notes Fields are space-separated and appear in the order listed above. Time values (utime, stime, etc.) are measured in jiffies (clock ticks). Divide by sysconf(_SC_CLK_TCK) to convert to seconds. References The /proc Filesystem linux kernel doc Linux kernel source: fs/proc/array.c ","date":"2026-03-29T23:08:22-07:00","permalink":"/blog/p/proc/pid/stat-field-reference/","title":"/proc/[PID]/stat Field Reference"},{"content":"\nTitle: 心象天儀本線 ~per aspera ad astra~ Developer: Atelier Ueshima Erika VNDB: https://vndb.org/v54933 Official Website: https://www.hoshiorigakuen.com/ Steam: https://store.steampowered.com/app/3664060/\nDemo release for the doujin visual novel 心象天儀本線 ~Per Aspera Ad Astra~, demo dropped January 29, 2026, ahead of the full release later in 2026. It caught my attention when I looking up new game on Steam, because of it uniq doujin art style, 4:3 resolution and the incredibly ancient UI give it a classic visual novel feel, it reminds me of early 2000–2010 visual novels.\nIt\u0026rsquo;s a debut doujin visual novel from Atelier Ueshima Erika — a small indie studio out of Taiwan, as far as I can tell — and you play as 上島椿 (Ueshima Tsubaki) at the end of the demo, a mysterious girl reveals that it's actually Ueshima Tsubaki who has been talking to you — the player — all along in train , a transfer student arriving at the 星織學園 (Hoshiori Academy). The story is told through what the game calls \u0026ldquo;touching tickets\u0026rdquo;: fragmented vignettes that slowly build toward something larger. A Literature Club. A galactic train. Recurring dreams. It shouldn\u0026rsquo;t cohere, and yet it does.\nWhat gets me is how intentional it all feels. The dreamlike art and surreal structure don\u0026rsquo;t come across as aesthetic choices slapped on top — they feel load-bearing, like the whole thing would collapse without them. And the soundtrack by composer 10lulu (60+ tracks planned for the full release, apparently), which I really like, its doing a lot of heavy lifting in the best possible way.\nThe full release is planned for later in 2026, promising 7–9 hours of playtime. If the demo is fit your type, this is one to keep on your wishlist. :L I will make another post once it released and I when through. :L\n","date":"2026-03-29T21:23:52-07:00","image":"/blog/p/%E5%BF%83%E8%B1%A1%E5%A4%A9%E5%84%80%E6%9C%AC%E7%B7%9A-~per-aspera-ad-astra~-demo/1.png","permalink":"/blog/p/%E5%BF%83%E8%B1%A1%E5%A4%A9%E5%84%80%E6%9C%AC%E7%B7%9A-~per-aspera-ad-astra~-demo/","title":"心象天儀本線 ~Per Aspera Ad Astra~ Demo"}]