FreeCampus Python

Unit Challenge: Break the Mirror Vault

Repair a magical cloning spell with independent nested state, hashable room markers, deliberate in-place and returned-copy APIs, and safe snapshots.
python-foundations mutability-identity-copying unit-challenge
Open in Colab
  • Level: Python Foundations · Unit 6 challenge
  • Estimated time: 60–120 minutes
  • Outcome assessed: Trace and repair nested aliases, choose stable room keys, and compose explicit mutation, copy, default, and snapshot contracts.
  • Evidence: Pass 24 progressive assertions, preserve one debugging record, and explain the derived escape artifact.

1. Break a cloning spell without hard-coding the escape

The Mirror Vault creates adventurer doppelgängers from one blueprint. Its broken spell copies only names: explorers share inventories, journals, routes, and visited-room state. When Nova collects a rune, Sol’s journal changes too.

Build a small ownership-aware state API that produces independently editable explorers while safely sharing immutable vault rules. Nova must collect three runes and derive this final artifact:

NOVA BREAKS THE MIRROR VAULT WITH 3 RUNES

Your implementation must make these behaviors visible:

  1. spawning creates new outer and nested mutable owners;
  2. tuple room coordinates work as stable set members;
  3. one command mutates a named explorer intentionally;
  4. one transformer returns independent state and preserves its source;
  5. a snapshot does not expose internal lists;
  6. omitted history gets fresh per-call storage; and
  7. a shallow-copy variation reproduces the curse and is then repaired.
NoteKeep the complete solution closed on your first attempt

Work for one or two focused hours. Run one assertion group at a time. Before opening a hint, identify the exact access path whose identity or value differs from the contract.

2. Read the blueprint and ownership rules

The blueprint contains:

  • immutable role and difficulty strings;
  • an immutable tuple of vault rules that may be shared safely;
  • a mutable inventory list;
  • a mutable journal dictionary containing rune and route lists; and
  • a mutable set of visited tuple coordinates.

Constraints

  1. Preserve all supplied public names, signatures, and docstrings.
  2. Do not mutate blueprint while spawning or operating on an explorer.
  3. Give each spawned explorer independent ownership of every mutable descendant.
  4. Preserve the identity of the immutable rules tuple deliberately.
  5. Use tuple coordinates such as (1, 0) as room markers; do not convert printed hashes into stored identifiers.
  6. record_rune_in_place must mutate its supplied explorer and return None.
  7. with_supply must return independently editable state and preserve its source.
  8. journal_snapshot must not return either internal list directly.
  9. remember_attempt must not share an omitted mutable default between calls.
  10. Derive the final message from explorer state; do not assign the solved sentence directly in vault_message.
  11. Keep calculation functions free of printing.
  12. Use only the standard library. Classes, files, serialization, and exception handlers are outside this challenge.

3. Start from the contract

Run the blueprint unchanged:

blueprint = {
    "name": None,
    "role": "vault scout",
    "difficulty": "foundation",
    "rules": (
        "record evidence",
        "preserve the source",
        "trust tuple coordinates",
    ),
    "inventory": ["lantern", "rope"],
    "journal": {
        "runes": [],
        "route": [],
    },
    "visited": {(0, 0)},
}

Then copy this scaffold. Implement functions in the order shown:

def spawn_explorer(blueprint, *, name):
    """Return an explorer with independent mutable state and shared rules."""
    raise NotImplementedError


def identity_report(left, right):
    """Return labeled identity evidence for important explorer paths."""
    raise NotImplementedError


def record_rune_in_place(explorer, room, rune):
    """Record room and rune in caller-owned explorer; return None."""
    raise NotImplementedError


def with_supply(explorer, supply):
    """Return independent explorer state containing one additional supply."""
    raise NotImplementedError


def journal_snapshot(explorer):
    """Return an immutable snapshot of rune and route sequences."""
    raise NotImplementedError


def remember_attempt(attempt, history=None):
    """Append attempt to supplied history or a fresh per-call list; return it."""
    raise NotImplementedError


def vault_message(explorer):
    """Return the explorer's derived Mirror Vault escape message."""
    raise NotImplementedError

identity_report(left, right) returns exactly these keys:

{
    "same_outer": False,
    "same_inventory": False,
    "same_journal": False,
    "same_runes": False,
    "same_route": False,
    "same_visited": False,
    "same_rules": True,
}

The values above describe two correctly spawned explorers from the same blueprint.

4. Build the vault state in observable stages

  1. Write an ownership table for every blueprint field. Mark immutable values as safe to share and mutable descendants as independently owned.
  2. Implement spawn_explorer with selective copying. Check the outer dictionary, then every nested mutable path before performing a mutation.
  3. Implement identity_report with is comparisons only. It reports relationships; it does not decide whether current values are correct.
  4. Implement the in-place rune command. Add the tuple room to visited, append it to the journal route, append the rune, and rely on the implicit None result.
  5. Implement with_supply by creating independent state first and then changing only the new inventory.
  6. Convert journal lists to tuples for the returned snapshot.
  7. Use None to create fresh history on omitted-argument calls while documenting that an explicitly supplied history is updated.
  8. Count the explorer’s runes inside vault_message; do not store the final count or sentence separately.
  9. Run the shallow-copy variation last so its deliberate leak cannot contaminate the main blueprint.

Draw the path nova -> journal -> runes and the matching Sol and blueprint paths. The three final list objects must be distinct even when their initial contents are equal.

5. Run progressive assertions

Run these 24 numbered assertions unchanged. A failed identity assertion points to an ownership boundary; a failed value assertion points to an operation or result contract.

Spawn and identity evidence

from copy import deepcopy

before = deepcopy(blueprint)
nova = spawn_explorer(blueprint, name="Nova")
sol = spawn_explorer(blueprint, name="Sol")

assert nova["name"] == "Nova"                                        # 1
assert sol["name"] == "Sol"                                          # 2
assert blueprint == before                                            # 3
assert identity_report(nova, sol) == {                                # 4
    "same_outer": False,
    "same_inventory": False,
    "same_journal": False,
    "same_runes": False,
    "same_route": False,
    "same_visited": False,
    "same_rules": True,
}
assert nova["rules"] is blueprint["rules"]                            # 5

Deliberate mutation and hashable room evidence

result = record_rune_in_place(nova, (1, 0), "SUN")

assert result is None                                                  # 6
assert nova["journal"]["runes"] == ["SUN"]                           # 7
assert nova["journal"]["route"] == [(1, 0)]                           # 8
assert (1, 0) in nova["visited"]                                      # 9
assert sol["journal"]["runes"] == []                                 # 10
assert blueprint["journal"]["runes"] == []                           # 11

A list coordinate such as [1, 0] is unhashable and cannot be a set member. Keep this direct failure commented during the normal run:

# [1, 0] in nova["visited"]

Returned-copy and snapshot evidence

powered = with_supply(nova, "mirror lens")

assert powered["inventory"] == ["lantern", "rope", "mirror lens"]     # 12
assert powered is not nova                                             # 13
assert powered["inventory"] is not nova["inventory"]                   # 14
assert powered["journal"] is not nova["journal"]                       # 15
assert nova["inventory"] == ["lantern", "rope"]                       # 16

snapshot = journal_snapshot(nova)
assert snapshot == {"runes": ("SUN",), "route": ((1, 0),)}           # 17
assert isinstance(snapshot["runes"], tuple) and isinstance(             # 18
    snapshot["route"], tuple
)

Fresh default and final artifact evidence

first_attempt = remember_attempt("left mirror")
second_attempt = remember_attempt("right mirror")

assert first_attempt == ["left mirror"]                                # 19
assert second_attempt == ["right mirror"]                              # 20
assert first_attempt is not second_attempt                              # 21

record_rune_in_place(nova, (1, 1), "MOON")
record_rune_in_place(nova, (2, 1), "STAR")

artifact = vault_message(nova)
assert (                                                               # 22
    artifact == "NOVA BREAKS THE MIRROR VAULT WITH 3 RUNES"
    and blueprint == before
    and sol["journal"]["runes"] == []
)

Reproduce and repair the shallow-copy curse

Use a separate fixture so deliberate failure evidence does not damage the main blueprint:

leaky_source = deepcopy(blueprint)
leaky_clone = leaky_source.copy()
leaky_clone["journal"]["runes"].append("SHADOW")

assert leaky_source["journal"]["runes"] == ["SHADOW"]                  # 23

repaired = spawn_explorer(leaky_source, name="Echo")
record_rune_in_place(repaired, (9, 9), "LIGHT")
assert (                                                               # 24
    leaky_source["journal"]["runes"] == ["SHADOW"]
    and repaired["journal"]["runes"] == ["SHADOW", "LIGHT"]
    and repaired["journal"]["runes"] is not leaky_source["journal"]["runes"]
)
WarningDo not repair a leak by weakening identity evidence

Two lists can contain equal values and still need independent future ownership. Keep both value and identity assertions when the contract promises isolated mutation.

6. Use the hint ladder only when needed

Hint 1: mark the intended object graph

The new outer dictionary, inventory list, journal dictionary, rune list, route list, and visited set need new identities for every explorer. Immutable strings may be reused, and the supplied rules tuple should remain the exact shared object. Check one access path at a time.

Hint 2: match each function to one ownership boundary

record_rune_in_place follows the supplied explorer and mutates its three relevant descendants. with_supply first calls the same safe spawning/copying logic with the explorer’s existing name, then appends only to the new inventory. The snapshot converts internal lists to tuples. The history helper constructs a list only when its parameter is None.

Hint 3: assemble the selective copy and artifact

Pseudocode:

new explorer = shallow outer copy
replace name
copy inventory
create journal dictionary with copied runes and route
copy visited set
reuse rules tuple deliberately

message = uppercase name + vault phrase + length of runes + plural noun

The shallow variation leaks because its new outer dictionary still holds the source journal reference. spawn_explorer repairs every mutable path.

7. Keep debugging evidence

Preserve one failed assertion from before your repair. Connect it to one exact path rather than writing only “the copy was wrong.”

Failure Value and identity evidence Ownership hypothesis Controlled change Verified rerun
Which numbered assertion failed? What were ==, is, and relevant contents? Which outer or nested object is shared incorrectly? Which one construction/copy/mutation changed? Which earlier and new assertions now pass?

Useful failures include a shared journal dictionary, independently copied journal with a still-shared rune list, a returned internal list, a reused default history, or a list room marker rejected as unhashable.

8. Compare with a complete solution

Open this only after attempting each assertion group and using hints in order.

Show one complete Mirror Vault solution
def spawn_explorer(blueprint, *, name):
    """Return an explorer with independent mutable state and shared rules."""
    explorer = blueprint.copy()
    explorer["name"] = name
    explorer["inventory"] = blueprint["inventory"].copy()
    explorer["journal"] = {
        "runes": blueprint["journal"]["runes"].copy(),
        "route": blueprint["journal"]["route"].copy(),
    }
    explorer["visited"] = blueprint["visited"].copy()
    explorer["rules"] = blueprint["rules"]
    return explorer


def identity_report(left, right):
    """Return labeled identity evidence for important explorer paths."""
    return {
        "same_outer": left is right,
        "same_inventory": left["inventory"] is right["inventory"],
        "same_journal": left["journal"] is right["journal"],
        "same_runes": left["journal"]["runes"] is right["journal"]["runes"],
        "same_route": left["journal"]["route"] is right["journal"]["route"],
        "same_visited": left["visited"] is right["visited"],
        "same_rules": left["rules"] is right["rules"],
    }


def record_rune_in_place(explorer, room, rune):
    """Record room and rune in caller-owned explorer; return None."""
    explorer["visited"].add(room)
    explorer["journal"]["route"].append(room)
    explorer["journal"]["runes"].append(rune)


def with_supply(explorer, supply):
    """Return independent explorer state containing one additional supply."""
    updated = spawn_explorer(explorer, name=explorer["name"])
    updated["inventory"].append(supply)
    return updated


def journal_snapshot(explorer):
    """Return an immutable snapshot of rune and route sequences."""
    return {
        "runes": tuple(explorer["journal"]["runes"]),
        "route": tuple(explorer["journal"]["route"]),
    }


def remember_attempt(attempt, history=None):
    """Append attempt to supplied history or a fresh per-call list; return it."""
    if history is None:
        history = []
    history.append(attempt)
    return history


def vault_message(explorer):
    """Return the explorer's derived Mirror Vault escape message."""
    name = explorer["name"].upper()
    rune_count = len(explorer["journal"]["runes"])
    return f"{name} BREAKS THE MIRROR VAULT WITH {rune_count} RUNES"

9. Predict another ownership change

Suppose the vault adds mutable settings = {"sound": True} to the blueprint. Before editing code, predict:

  • whether the current spawn_explorer shares that dictionary;
  • which identity-report field you would add;
  • whether settings should be shared or independently editable;
  • the minimum copy change for that ownership decision; and
  • which source-preservation assertion should fail before the repair.

This variation checks whether you can extend the object graph rather than merely remember the existing field names.

10. Check your understanding

Answer from the completed object graph and assertions rather than the story alone.

11. Decide whether the challenge is complete

Evidence rubric

Evidence Ready to record when
Object graph You can identify every intentionally shared and independently owned path.
Behavior All 24 assertions pass unchanged from a clean state.
Mutation API The in-place command changes only its supplied explorer and returns None.
Copy API The transformer returns independently editable state and preserves its source.
Boundaries Tuple markers, safe snapshots, and fresh default history behave as documented.
Debugging One failed assertion is connected to a precise shared path and verified repair.
Artifact The final sentence is derived from Nova’s name and collected rune count.

Record completion only when every statement is true:

This button stores a self-reported marker only in this browser. It does not submit the artifact, grade it, verify identity, or issue a certificate.

Not yet recorded.

Key points

  • Object graphs make accidental sharing observable before a mutation spreads.
  • Selective copying can isolate every mutable descendant while safely reusing an immutable value whose sharing is intentional.
  • Hashable tuple coordinates make visited-room membership stable and meaningful.
  • In-place commands, returned-copy transformers, snapshots, and optional defaults each need explicit ownership contracts.
  • A shallow-copy failure is useful evidence when it identifies the exact nested reference that remained shared.
  • The successful escape sentence is derived from independently owned state, not hidden directly in the implementation.
Back to top