Skip to content

NPC Claim Violation and Shop Theft Model #141

@PJensen

Description

@PJensen

NPC Claim Violation and Shop Theft Model

Goal

NPCs should turn on the player when the player violates a recognized claim.

This should not be modeled as random NPC anger or a special-case shopkeeper reaction. It should be modeled as a rules-side claim violation system that can support shop theft, trespass, faction retaliation, sacred desecration, assault, debt, and future reputation mechanics.

The immediate bug/example:

A player walked into a book shop, picked up a spellbook, learned a prayer from it for free, and the shopkeeper did not react.

That is wrong. Reading an unpaid spellbook is not harmless inspection. It transfers value from owned property to the player without payment. The player extracted benefit from the item even if they did not physically remove it.

The model should treat this as unauthorized use of owned property: knowledge theft.

Core Design Principle

Do not model this as:

shopkeeper gets mad because player picked up item

Model it as:

player violated a claim recognized by an NPC or faction

The player can violate different kinds of claims:

property claim
territory claim
body claim
kin / faction claim
sacred claim
contract / debt claim

Shop theft is only one instance of the broader rule.

Immediate Behavior Target

For shop items:

Picking up unpaid merchandise inside the shop may be allowed.
Using unpaid merchandise is not allowed.
Leaving the shop with unpaid merchandise is not allowed.
Destroying unpaid merchandise is not allowed.
Refusing payment after unauthorized use escalates the situation.
Attacking the owner escalates immediately.

For the spellbook case:

Reading an unpaid spellbook in a shop should create debt or a transgression.
Learning a prayer/spell from that book counts as value extraction.
The shopkeeper should react.
Reaction should usually begin with warning/demand, not instant combat.

Suggested first reaction:

"That knowledge is not free. Pay 120 gold."

New Concepts

Ownership

Represents a property claim over an entity.

Example shape:

Ownership {
  ownerId,       // entity id of owner, if specific NPC owns it
  factionId,     // optional faction claim, e.g. "shopkeepers"
  price,         // sale value / debt value
  saleState      // "for_sale" | "paid" | "stolen" | "used"
}

Notes:

  • ownerId is useful for a specific shopkeeper.
  • factionId lets the faction remember crimes even if the specific owner dies.
  • price is the default debt amount.
  • saleState should remain semantic. Avoid visual/display fields here.

UsePolicy

Represents whether using an item requires ownership or payment.

Example shape:

UsePolicy {
  requiresPurchase,        // true for shop merchandise
  unauthorizedUseKind,     // "knowledge_theft" | "consumption_theft" | "activation_theft"
  valueOnUse,              // fraction of price owed on unauthorized use
  consumeOnUse             // whether the item disappears after use
}

Examples:

Spellbook:
  unauthorizedUseKind = "knowledge_theft"
  valueOnUse = 1.0
  consumeOnUse = false

Potion:
  unauthorizedUseKind = "consumption_theft"
  valueOnUse = 1.0
  consumeOnUse = true

Scroll:
  unauthorizedUseKind = "activation_theft"
  valueOnUse = 1.0
  consumeOnUse = true

Transgression

A fact that the player violated a recognized claim.

This may be stored as a component, record, or rules-side event depending on the current architecture.

Example shape:

Transgression {
  actorId,
  victimId,
  factionId,
  kind,
  itemId,
  value,
  turn,
  witnessed
}

Example for the spellbook:

Transgression {
  actorId: playerId,
  victimId: shopkeeperId,
  factionId: "shopkeepers",
  kind: "knowledge_theft",
  itemId: spellbookId,
  value: 120,
  turn: world.turn,
  witnessed: true
}

Debt

Represents an unresolved claim for payment.

Example shape:

Debt {
  debtorId,
  creditorId,
  factionId,
  amount,
  reason,
  itemId,
  createdTurn,
  status       // "unpaid" | "paid" | "refused" | "forgiven"
}

For the first implementation, debt may be enough without persistent reputation.

Disposition

Represents how one entity currently regards another.

Example shape:

Disposition {
  sourceId,
  targetId,
  stance,      // "neutral" | "suspicious" | "warn" | "angry" | "hostile"
  reason
}

Important: AI should read disposition. AI should not independently decide that a crime occurred.

Suggested Rule Flow

Reading / Using an Item

When the player performs an item-use intent:

Intent: read / quaff / eat / activate item

Rules:
  if item has Ownership
  and item is not paid for
  and actor is not owner
  and item has UsePolicy requiring purchase
    create Transgression
    create or update Debt
    update owner/faction Disposition toward actor
    emit semantic event

  then continue or block the use depending on policy

For spellbooks, the use probably should still succeed. The player learned the prayer; now they owe money.

For some magical shops or sacred texts, future variants may block the use until paid. But the first version should allow the theft and create consequences.

Leaving With Owned Goods

When the player attempts to leave the shop region:

if player carries unpaid owned items belonging to shopkeeper/shop faction:
  create theft transgression or escalate existing debt
  shopkeeper demands payment
  if player refuses / forces exit:
    hostile or guards summoned

This can come after the read/use case. It is the second vertical slice.

Destroying Owned Goods

When an owned item is destroyed:

if item has Ownership and is unpaid:
  create property_damage transgression
  create Debt for item price
  update owner Disposition

Shopkeeper Escalation Ladder

The shopkeeper should not always instantly attack.

Suggested ladder:

1. Neutral
   Player is browsing.

2. Warning
   Player used/read an unpaid item or picked up too many items.
   Shopkeeper says: "Careful. That is not yours yet."

3. Demand Payment
   Player has unpaid debt.
   Shopkeeper says: "Pay 120 gold."

4. Containment
   Player tries to leave or ignores the demand.
   Shopkeeper blocks the exit, locks door, or calls guards.

5. Criminal Mark
   Player refuses payment, flees, or repeats the offense.
   Debt becomes transgression/reputation with shopkeeper faction.

6. Hostile
   Player attacks, refuses, flees with goods, or escalates past threshold.

Good first implementation:

Unauthorized use creates debt and sets shopkeeper stance to "angry" or "warn".
Trying to leave with unpaid debt sets stance to "hostile" or summons guards.
Attacking the shopkeeper sets stance to "hostile" immediately.

First Vertical Slice

Implement this in the smallest useful path.

Components / Data

Add or reuse:

Ownership
UsePolicy
Debt or Transgression
Disposition
FactionMember

Apply to Book Shop Items

For shop spellbooks:

Ownership:
  ownerId = shopkeeper id
  factionId = "shopkeepers"
  price = current shop price
  saleState = "for_sale"

UsePolicy:
  requiresPurchase = true
  unauthorizedUseKind = "knowledge_theft"
  valueOnUse = 1.0
  consumeOnUse = false

Hook Item Use

In the rules-side item use/read path, before or after the learn effect:

function handleUnauthorizedUse(world, actorId, itemId) {
  const ownership = getOwnership(world, itemId);
  const policy = getUsePolicy(world, itemId);

  if (!ownership || !policy) return null;
  if (!policy.requiresPurchase) return null;
  if (ownership.saleState === "paid") return null;
  if (ownership.ownerId === actorId) return null;

  const amount = Math.ceil((ownership.price || 0) * (policy.valueOnUse ?? 1));

  const debt = createDebt(world, {
    debtorId: actorId,
    creditorId: ownership.ownerId,
    factionId: ownership.factionId,
    amount,
    reason: policy.unauthorizedUseKind,
    itemId,
  });

  createTransgression(world, {
    actorId,
    victimId: ownership.ownerId,
    factionId: ownership.factionId,
    kind: policy.unauthorizedUseKind,
    itemId,
    value: amount,
    witnessed: true,
  });

  setDisposition(world, {
    sourceId: ownership.ownerId,
    targetId: actorId,
    stance: "warn",
    reason: policy.unauthorizedUseKind,
  });

  emitEvent(world, {
    type: "claim_violated",
    actorId,
    victimId: ownership.ownerId,
    factionId: ownership.factionId,
    kind: policy.unauthorizedUseKind,
    itemId,
    value: amount,
  });

  return debt;
}

Names should be adapted to the existing ECS style. This is intended as shape, not exact final API.

Shopkeeper AI Response

Shopkeeper behavior should check debt/disposition:

if player owes debt to this shopkeeper:
  approach player
  demand payment
  block exit if player tries to leave

if player attacks shopkeeper:
  become hostile

if player refuses payment or flees:
  become hostile / call guards

Do not put theft detection inside shopkeeper AI. The rules system should produce debt/transgression/disposition. AI responds to those facts.

Bridge / Display Contract

The rules layer should emit semantic facts only.

Good event:

{
  type: "claim_violated",
  actorId,
  victimId,
  factionId,
  kind,
  itemId,
  value
}

Good event:

{
  type: "debt_created",
  debtorId,
  creditorId,
  amount,
  reason,
  itemId
}

Good event:

{
  type: "disposition_changed",
  sourceId,
  targetId,
  stance,
  reason
}

Bad event:

{
  type: "shopkeeper_turns_red_and_shakes"
}

Display can choose glyphs, colors, speech bubbles, anger particles, alert marks, etc. Rules should only expose semantic state and events.

Suggested Event Names

Use existing event conventions if available. Otherwise:

claim_violated
transgression_recorded
debt_created
debt_paid
debt_refused
disposition_changed

No lighting, VFX, color, camera, or animation information should be introduced into rules events.

Edge Cases

Player Reads Book But Already Paid

No transgression.

Player Reads Book Owned by Self

No transgression.

Player Reads Book Not For Sale But Owned

This is still unauthorized use, but reason may be:

forbidden_knowledge
sacred_violation
private_property_violation

Not all owned books are shop merchandise.

Player Picks Up Item But Does Not Use It

No immediate theft unless the shop design wants stricter rules.

Recommended default:

Pickup inside shop is suspicious but tolerated.
Leaving with unpaid item is theft.
Using unpaid item is theft.

Player Has Enough Gold

On unauthorized use, the shopkeeper can demand payment. Later, interaction can offer:

Pay now
Refuse
Drop item does not help if value was already extracted

For knowledge theft, dropping the book does not undo the theft.

Player Does Not Have Enough Gold

Debt remains unpaid.

Shopkeeper/faction can escalate.

Player Learns Same Prayer Already Known

This is subtle.

Options:

A. Still theft, because they used the merchandise.
B. Lesser violation, because no new value was extracted.
C. No violation, because no benefit was gained.

Recommended first behavior: still create violation. Simpler and more legible.

Later refinement: if no new knowledge was learned, debt amount could be reduced.

Item Use Is Accidental

No special handling in first version. Player intent is enough.

Owner Not Present

If unwitnessed, create transgression with witnessed: false or create no immediate reaction.

Future stealth support:

unwitnessed theft affects inventory state but not immediate disposition
witnessed theft affects disposition immediately
magical shops may always witness knowledge theft

First version can assume shopkeeper witnesses use inside shop.

Multiple Shopkeepers / Faction Store

Prefer both:

ownerId: specific shopkeeper
factionId: shopkeepers

The specific NPC demands payment. The faction may remember repeat offenses.

Why This Is Worth Doing

This creates a reusable moral/social physics layer.

Same system can power:

shop theft
reading forbidden books
using sacred altars incorrectly
attacking allied NPCs
killing faction members
stealing from homes
breaking contracts
entering restricted rooms
refusing debt

NPCs become more interesting because they are not merely hostile/non-hostile. They are defending claims.

Acceptance Tests

Test 1: Reading Unpaid Spellbook Creates Debt

Given a shopkeeper owns a spellbook for sale
And player has not paid for it
When player reads the spellbook
Then a Debt exists from player to shopkeeper
And the debt amount equals the spellbook price
And a claim/transgression event is emitted
And shopkeeper disposition toward player is warn/angry

Test 2: Reading Paid Spellbook Does Not Create Debt

Given player has purchased the spellbook
When player reads it
Then no transgression is created
And no new debt is created
And shopkeeper disposition does not worsen

Test 3: Picking Up Book Alone Does Not Create Theft

Given a shop spellbook is unpaid
When player picks it up inside the shop
Then no debt is created
And no transgression is created

Test 4: Leaving With Unpaid Item Escalates

Given player carries an unpaid shop item
When player crosses the shop exit boundary
Then theft transgression is created
And shopkeeper disposition escalates

This can be implemented after Test 1 if exit/shop-region logic is not ready.

Test 5: Destroying Unpaid Item Creates Debt

Given an unpaid shop item exists
When player destroys it
Then property_damage transgression is created
And player owes the item value

Optional for first pass.

Implementation Priority

  1. Add ownership to shop merchandise.
  2. Add use policy to spellbooks, scrolls, potions, and other usable shop items.
  3. Hook unauthorized-use detection into the item-use/read path.
  4. Create debt/transgression and disposition state.
  5. Make shopkeeper AI respond to debt/disposition.
  6. Add leaving-with-unpaid-goods detection later.
  7. Add faction/reputation memory later.

Final Rule of Thumb

An NPC turns on the player when the player violates a claim the NPC recognizes.

For shops, the claims are property and payment.

For spellbooks, reading can be theft because the player can steal the value without stealing the object.

That is the mechanic to preserve.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions