惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tor Project blog
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 【当耐特】
G
Google Developers Blog
J
Java Code Geeks
The Cloudflare Blog
Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
Cisco Talos Blog
Cisco Talos Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
I
Intezer
Jina AI
Jina AI
T
Tenable Blog
P
Palo Alto Networks Blog
Project Zero
Project Zero
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
The Hacker News
The Hacker News
F
Full Disclosure
Cloudbric
Cloudbric
量子位
H
Heimdal Security Blog
K
Kaspersky official blog
有赞技术团队
有赞技术团队
罗磊的独立博客
V
Vulnerabilities – Threatpost
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
阮一峰的网络日志
阮一峰的网络日志
Vercel News
Vercel News
Recent Announcements
Recent Announcements
WordPress大学
WordPress大学
GbyAI
GbyAI
S
SegmentFault 最新的问题
M
MIT News - Artificial intelligence
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
Recorded Future
Recorded Future
Security Archives - TechRepublic
Security Archives - TechRepublic
AI
AI
Webroot Blog
Webroot Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
The Exploit Database - CXSecurity.com
Apple Machine Learning Research
Apple Machine Learning Research
C
Cybersecurity and Infrastructure Security Agency CISA
H
Hacker News: Front Page
Latest news
Latest news

Hacker News: Front Page

SPICE simulation → oscilloscope → verification with Claude Code — Lucas Gerads GitHub - GainSec/AutoProber: Hardware hacker’s flying probe automation stack for agent-driven target discovery, microscope mapping, safety-monitored CNC motion, probe review, and controlled pin probing. Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Virginia Bans Sale of Geolocation Data Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Ancient DNA reveals pervasive directional selection across West Eurasia [pdf] AI cybersecurity is not proof of work Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. A Better Ludum Dare; Or, How to Ruin a Legacy GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Unexpected €54k billing spike in 13 hours: Firebase browser key without API restrictions used for Gemini requests Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent Codex Hacked a Samsung TV Tech Valuations Back to Pre-AI Boom Levels A perfectable programming language — Soter GitHub - halfwhey/claudraband: Claude Code for the Power User Partnership through Play: Investigating How Long-Distance Couples Use Digital Games to Facilitate Intimacy Textbooks and Methods of Note-Taking in Early Modern Europe (2008) Eternity in six hours: Intergalactic spreading of intelligent life (2013) Seven countries now generate 100% of their electricity from renewable energy Tell HN: OpenAI silently removed Study Mode from ChatGPT Pro Max 5x Quota Exhausted in 1.5 Hours Despite Moderate Usage Show HN: Oberon System 3 runs natively on Raspberry Pi 3 (with ready SD card) Tell HN: docker pull fails in spain due to football cloudflare block Bring Back Idiomatic Design No one owes you supply-chain security GitHub - xsawyerx/curl-doom: DOOM, played over cURL Apple update turns Czech mate for locked-out iPhone user The Grand Line Cache TTL silently regressed from 1h to 5m around early March 2026, causing quota and cost inflation Building a Z-Machine in the worst possible language The peril of laziness lost Iran war: We spoke to the man making Lego-style AI videos that experts say are powerful propaganda AI Will Be Met With Violence, and Nothing Good Will Come of It GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The disturbing white paper Red Hat is trying to erase from the internet – OSnews NetBlocks (@netblocks@mastodon.social) The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong The FAA wants gamers to apply for air traffic control jobs Artemis II crew splashes down near San Diego after historic moon mission Why weekends are under threat We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Keeping a Postgres queue healthy — PlanetScale Serenity Forge (@serenityforge.com) Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? Uncharted island soon to appear on nautical charts The Problem That Built an Industry Fragments: April 2 Python Release Python install manager 26.1 Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Harness engineering: leveraging Codex in an agent-first world Apple Silicon and Virtual Machines: Beating the 2 VM Limit What have been the greatest intellectual achievements? The APL Programming Language Source Code
The Low-Tech AI Of Elden Ring
nega.tv via · 2026-06-23 · via Hacker News: Front Page

FROMSOFT has a reputation for diverse and punishing npc encounters across the entire Soulsborne extended series, but the implementation of the AI decision making itself is perhaps unexpectedly low-tech. Since the majority of the code is implemented in Havok Script (A games-oriented Lua implementation from Havok) it’s pretty easy to take a peek behind the fog wall to see how they’re implemented.

Note that none of what follows is original research, I’m just reading the code that others have done the hard work of extracting, decompiling, and reversing.

Goals

The primary tool of the FROMSOFT AI approach is the Goal1, which is their own terminology for a unique state that the AI can be in. Goals can be parametized when instanciated, and can access data stored on the Actor itself, but are otherwise really just an immutable table of functions.

Now the simplest option would be to organize states into a Finite State Machine or maybe a Hierarchical Finite State Machine, but FROMSOFT go one step further and give the system a stack of states. This turns it from an FSM into Pushdown Automaton (PDA).

That’s an entirely abstract definition, so after you get back from wikipedia let’s talk about it concretely from the top down.

Each frame Actors will update the Goal on top of their stack of Goals. When the Goal updates, it can then push more Goals as Sub-Goals onto the stack, the topmost of which will execute next frame. The Goal’s update function returns a value indicating either Continue, Success, or Failure. Continue will leave the stack unchanged, the other two will cause the Goal to be popped from the stack. Failure will additionally cause all other unexecuted Goals to be popped from the stack up to the parent Goal (The Goal which pushed this sub-goal).

For example, we might define a Goal called CoolBossBattle, during the course of its execution it might then push a series of Attack Sub-Goals. Those attack Goals can be parametized by various means, but the main one is the animation id2.

[ GOAL STACK ]

3: Attack (R2, Combo)           <<<<-- Currently Updating
2: Attack (R2, Repeat)
1: Attack (R2, Finisher)
0: CoolBossBattle

After a few seconds the first attack lands, and that Goal completes with success and is popped from the stack. However the next fails, causing the stack to unwind to its parent.

[ GOAL STACK ]

2: Attack (R2, Repeat)          <<<<-- Failed, will be popped from the stack.
1: Attack (R2, Finisher)        <<<<-- Will be removed as well.
0: CoolBossBattle

Readying it to chose its next action now that the attempted combo of attacks has ended.

[ GOAL STACK ]

2: Attack(L1)
1: Attack(L1)
0: CoolBossBattle               <<<<-- Updating, pushes 1, and 2 for the next frame.

Not too complex3!

In their APIs they refer to the root of this stack as the “Top Level Goal”, which I’ve made confusing by referring to the currently executing goal as the “top” of the stack. So keep in mind those are separate things.

Activate

Goals are defined by a few functions used as callbacks, and the one which contains the most AI logic is usually activate. This is called the first time that a Goal is updated, and then every subsequent time that the Goal exhausts its Sub-Goals and starts executing again.

For boss and regular npc Goals the code in Activate is responsible for choosing the next action that the Actor will take using a mix of context from the world and Actor, and randomness (which also comes from the Actor itself).

The most widely used approach uses common code to perform a weighted random selection between a number of Actions (which are just functions), calling the winner.

To return to our CoolBossBattle, this time in some Rusty pseudocode…

fn action_giga_death_ray(goals: &Goals, actor: &Actor) {
    todo!();
}

fn action_leap_attack(goals: &Goals, actor: &Actor) {
    todo!();
}

fn action_ground_slam(goals: &Goals, actor: &Actor) {
    todo!();
}

fn action_light_attack_combo(goals: &Goals, actor: &Actor) {
    let target_distance = actor.target_distance(Target::Enemy);
    let fate = actor.next_random();

    // ApproachTarget itself being a goal defined in common code!
    if target_distance > 2.0 {
        goals.push_sub_goal(Goal::ApproachTarget, Target::Enemy);
    }

    goals.push_sub_goal(Goal::Attack, AnimId::R1, Combo::Initial);
    goals.push_sub_goal(Goal::Attack, AnimId::R1, Combo::Repeat);

    // Unlucky buster! It's the long combo.
    if fate < 0.2 {
        goals.push_sub_goal(Goal::Attack, AnimId::R1, Combo::Repeat);
    }

    goals.push_sub_goal(Goal::Attack, AnimId::R1, Combo::Finisher);
}

fn action_heavy_attack_combo(goals: &Goals, actor: &Actor) {
    todo!();
}

fn activate(&self, goals: &Goals, actor: &Actor) {
    let target_distance = actor.target_distance(Target::Enemy);

    let mut weights = if target_distance > 6.0 {
        [
            15.0,
            65.0,
            0.0,
            10.0,
            10.0,
        ]
    } else if target_distance > 1.5 {
        [
            0.0,
            0.0,
            5.0,
            60.0,
            35.0,
        ]
    } else {
        [
            0.0,
            0.0,
            20.0,
            40.0,
            40.0,
        ]
    };

    // This doesn't exactly work this way in the Lua code, and these cooldowns
    // don't make sense either, but hopefully it gives the rough idea.
    //
    // The helper function is checking last played data for the animation on the
    // Actor itself, and then modifying the weights before they go into the
    // common battle randomized selection.
    weights[3] = if common::is_cooldown(goals, actor, AnimId::R1, 8.0) { 0.0 } else { weights[3]; };
    weights[4] = if common::is_cooldown(goals, actor, AnimId::R2, 10.0) { 0.0 } else { weights[4]; };

    let actions = [
        action_giga_death_ray,
        action_leap_attack,
        action_ground_slam,
        action_light_attack_combo,
        action_heavy_attack_combo,
    ];

    // Does some common setup for the number of actions and then rolls the dice
    // and chooses which function to call.
    common::battle_activate(goals, actor, weights, actions);
}

Modifying the weights dynamically is handled in many different ways, but the most common are simple rng rolls from the actor and hp thresholding.

Other, simpler, Goals than the top level battle Goal for an Actor may simply push a few sub-goals, perhaps reading some data from the Goal parameters. The nesting means that it’s possible to compose quite complex behavior from simple building blocks.

Interrupts

The other major callback defined for goals is the Interrupt. As the name suggests, this allows Goals to respond immediately to external events which are mostly configured on the Actor itself.

My understanding is that interrupts bubble up, that is, it will run the interrupt on the currently executing Goal and then its parents recursively, until it runs out of Goals or one of the interrupt callbacks returns true to indicate it has consumed the interrupt.

For example, if I wanted CoolBoss to move into a furious rage of attacks as soon as I set it on fire, then I might implement something like the following.

fn interrupt(&self, goals: &Goals, actor: &Actor, interrupt: Interrupt) {
    match interrupt {
        // If I start burning, attack!
        SpecialEffectActivate {
            target,
            special_effect,
        } => {
            if target == Target::Self && special_effect == SpecialEffect::Fire {
                // Since there might still be other things running when
                // interrupt is called we need to unwind so we're on top again.
                goals.clear_sub_goals();

                goals.push_sub_goal(Goal::Attack, AnimId::R1);
                goals.push_sub_goal(Goal::Attack, AnimId::R2);
                goals.push_sub_goal(Goal::Attack, AnimId::R1);
                goals.push_sub_goal(Goal::Attack, AnimId::R2);

                return true;
            }
        }
        // If somebody uses an item they might be in for it.
        UseItem => {
            let fate = actor.next_random();
            if fate < 0.5 {
                goals.clear_sub_goals();
                action_light_attack_combo(goals, actor);
            }
        }
        // Perform a ground slam if I get attacked from underneath.
        Damage {
            target,
        } => {
            if target == Target::Self {
                let distance = actor.target_distance(Target::Enemy);
                let fate = actor.next_random();
                if distance < 1.0 && fate < 0.8 {
                    goals.clear_sub_goals();
                    action_ground_slam(goals, actor);
                }
            }
        }
        _ => {}
    }

    false
}

This is used to implement some truly evil features, for example the Bell Bearing Hunter will detect you spell casting or using an item and from there has an 85% chance to immediately abort its current action and launch into an attack.

They also make use of dynamic spatial watch regions configured on Actors, which trigger interrupts. For example you might add a watch for the area behind or under a boss, and use that to adapt their behavior immediately when the player tries to get clever.

Timeouts

Goals, in addition to their individual state, carry a lifetime value in seconds. This is used to break out of states which become stuck for whatever reason, and lifetime seems to be used mostly as a bug containment mechanism.

It’s also possible to modify the lifetime of a parent goal during execution, to indicate continued forward progress.

Actor Data Access

In many AI decision systems you might have heard of fancy systems for data storage like “blackboards”. In the Souls games there’s an array of floats on each Actor which are set and read arbitraily from Goals by index. Good enough I suppose!

A callback I didn’t mention before, Initialise, is commonly used to reset this data when an Actor is assigned a new Top Level Goal.

Goals have access to a range of queries about the world through the Actor. As far as I can tell most of these are pretty “low cost” from a performance perspective. Aggro and Targeting seems to be handled outside, so it should be possible to keep the Goals very lean even considering it’s all interpreted Lua.

Actual Doing Stuff

Something I’ve entirely skipped over is how the Goals actually Do things. For the most part everything in FROMSOFT games is animation driven.

The Goal says “play this attack animation”, and then the animation events carry hitbox information and timings, special effect triggers, projectile creation events, and whatnot. They also have a variety of “combo” features which seem to boil down to choosing a different set of events in the animations to enable faster linking of chained animation during a combo attack.

At some point they went all-in on Havok middleware. The animations are authored with Havok Animation Studio (discontinued). Previously we mentioned the AI scripts are using Havok Script (also discontinued). Physics is handled by Havok’s physics, and pathfinding is delegated to Havok AI (not discontinued, but renamed to Havok Navigation).

Misc Stuff

  1. They seem to split AI scripting into a “logic” script, and a “battle” script, where the logic script is far more sharable, and the battle scripts are often bespoke. This seems super smart, it’s common to run into issues jamming both these things into singular hierarchies.

  2. Level designers are able to configure the Top Level Goal for an Actor in the level itself, so you can place some enemies down with a passive Goal rather than their usual combat Goal, and they would just chill whilst otherwise functioning normally.

  3. Most of the common code is relatively compact bits of Lua, but I believe load bearing Goals like Attack and MoveToSomewhere are implemented in C++ which gives you a pretty nice balance of scriptability and performance sanity.

  4. The update function itself is sometimes used to check conditions, I expect this must have caused problems occasionally. But so long as the interface for Actors in scripting is thin I guess you can keep it under control. (Don’t add a pathfind function call…)

  5. I’ve entirely skipped over the event scripting system used to do high level encounter logic and level scripting. Unlike the AI it seems to be entirely custom, with a very restricted VM. That said, since it’s not Lua it’s hard to see how they’re actually authored. If anyone knows of primary sources for info about their tooling that would be super cool!

Conclusion

There’s a lot of enduring hype for complicated AI systems (GOAP springs to mind) but I think the success of putting a lot of control in the hands of your designers and animators really speaks for itself.

A pushdown automaton is also fundamentally quite fast compared to Behavior Trees and planners. Behavior Trees often require top-down re-evaluation of a complex tree of scripted nodes, whereas this is almost always executing a single Goal from the top of the stack4. Planners like STRIPS, GOAP and HTN add an expensive search to the middle of everything.

Compared to FSMs the flexibility of dynamic transitions makes it easier to avoid an explosion in the number of states and their transitions. This also makes it far more reasonable to compose AI functionality in an imperative way.

Plus of course it’s dramatically more legible than planner based solutions where individual actions are moved out of the hands of combat designers.

Is it going to handle more complex scenarios than the typical Soulsborne npc or boss fight? I actually think it can go quite far.

References

Most of the info in this post comes from eladidu readable ds lua it’s fantastic and you can find many interesting definitions as well as a little tutorial.

If you want to get even more excited there’s a bunch of tools for extracting data from the game packages, as well as nice modding tools for patching things here and there.

  1. This is not to be confused with the concept of a goal which you might know from advanced planning systems like STRIPS, GOAP (Goal Oriented Action Planning) or HTN (Hierarchical Task Networks). Those systems use a search algorithm to dynamically find a sequence of Actions which move the world into a Goal State. There’s nothing remotely so complex happening here!↩

  2. Animation ids are largely based on playstation controller inputs which are then offset by a per-actor value in the npc definition. Moveset swaps can be performed by changing the offset dynamically from scripts!↩

  3. I’m glossing over a small problem… You want to be able to write your scripts so that sub-goals function as a queue, not a stack, so they are executed in the order they’re pushed. Unfortunately that slightly complicates the implementation and explanation so I’ve left it as an exercise for the reader.↩

  4. I’m not entirely sure whether they update only the current Goal from the top, or whether they recursively update currently active goals, but I suspect it might be the latter. This is still dramatically more efficient than re-evaluating decision criteria in a behavior tree.↩